aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md188
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/README.rdoc3
-rw-r--r--actionpack/actionpack.gemspec4
-rw-r--r--actionpack/lib/abstract_controller.rb2
-rw-r--r--actionpack/lib/abstract_controller/base.rb1
-rw-r--r--actionpack/lib/abstract_controller/caching/fragments.rb20
-rw-r--r--actionpack/lib/abstract_controller/collector.rb1
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb30
-rw-r--r--actionpack/lib/abstract_controller/translation.rb1
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/caching.rb1
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb7
-rw-r--r--actionpack/lib/action_controller/metal/basic_implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb2
-rw-r--r--actionpack/lib/action_controller/metal/content_security_policy.rb1
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_template_digest.rb2
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb2
-rw-r--r--actionpack/lib/action_controller/metal/feature_policy.rb46
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb8
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb2
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb23
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb4
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb9
-rw-r--r--actionpack/lib/action_controller/metal/live.rb8
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb10
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb36
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb10
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb3
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb4
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb1
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb64
-rw-r--r--actionpack/lib/action_controller/renderer.rb6
-rw-r--r--actionpack/lib/action_controller/template_assertions.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb12
-rw-r--r--actionpack/lib/action_dispatch.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb35
-rw-r--r--actionpack/lib/action_dispatch/http/feature_policy.rb168
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/filter_redirect.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb28
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb159
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb3
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/builder.rb1
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb1
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb1
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb13
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb12
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb1
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/scanner.rb1
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb39
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb52
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb75
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_view.rb66
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb39
-rw-r--r--actionpack/lib/action_dispatch/middleware/host_authorization.rb101
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb37
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb0
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb75
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb11
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing.rb36
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb78
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb43
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb3
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb48
-rw-r--r--actionpack/lib/action_dispatch/system_testing/browser.rb45
-rw-r--r--actionpack/lib/action_dispatch/system_testing/driver.rb11
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb5
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb13
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb26
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb5
-rw-r--r--actionpack/lib/action_dispatch/testing/request_encoder.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb36
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb2
-rw-r--r--actionpack/test/abstract/collector_test.rb2
-rw-r--r--actionpack/test/abstract_unit.rb82
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb18
-rw-r--r--actionpack/test/controller/api/conditional_get_test.rb1
-rw-r--r--actionpack/test/controller/base_test.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb33
-rw-r--r--actionpack/test/controller/content_type_test.rb29
-rw-r--r--actionpack/test/controller/filters_test.rb21
-rw-r--r--actionpack/test/controller/flash_test.rb9
-rw-r--r--actionpack/test/controller/helper_test.rb4
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb2
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb7
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb2
-rw-r--r--actionpack/test/controller/integration_test.rb16
-rw-r--r--actionpack/test/controller/localized_templates_test.rb2
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb22
-rw-r--r--actionpack/test/controller/metal/renderers_test.rb4
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb1
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb99
-rw-r--r--actionpack/test/controller/new_base/content_negotiation_test.rb14
-rw-r--r--actionpack/test/controller/new_base/render_context_test.rb55
-rw-r--r--actionpack/test/controller/new_base/render_file_test.rb28
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb1
-rw-r--r--actionpack/test/controller/new_base/render_test.rb1
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb33
-rw-r--r--actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb1
-rw-r--r--actionpack/test/controller/parameters/mutators_test.rb9
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_permit_test.rb30
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb58
-rw-r--r--actionpack/test/controller/params_parse_test.rb1
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb1
-rw-r--r--actionpack/test/controller/redirect_test.rb29
-rw-r--r--actionpack/test/controller/render_js_test.rb3
-rw-r--r--actionpack/test/controller/render_json_test.rb15
-rw-r--r--actionpack/test/controller/render_test.rb28
-rw-r--r--actionpack/test/controller/render_xml_test.rb5
-rw-r--r--actionpack/test/controller/renderer_test.rb6
-rw-r--r--actionpack/test/controller/renderers_test.rb4
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb1
-rw-r--r--actionpack/test/controller/rescue_test.rb28
-rw-r--r--actionpack/test/controller/resources_test.rb5
-rw-r--r--actionpack/test/controller/routing_test.rb4
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb16
-rw-r--r--actionpack/test/controller/test_case_test.rb29
-rw-r--r--actionpack/test/controller/url_for_test.rb8
-rw-r--r--actionpack/test/controller/webservice_test.rb6
-rw-r--r--actionpack/test/dispatch/actionable_exceptions_test.rb80
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb1
-rw-r--r--actionpack/test/dispatch/content_security_policy_test.rb80
-rw-r--r--actionpack/test/dispatch/cookies_test.rb213
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb206
-rw-r--r--actionpack/test/dispatch/feature_policy_test.rb142
-rw-r--r--actionpack/test/dispatch/host_authorization_test.rb161
-rw-r--r--actionpack/test/dispatch/live_response_test.rb10
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb41
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb109
-rw-r--r--actionpack/test/dispatch/mount_test.rb15
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb10
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb58
-rw-r--r--actionpack/test/dispatch/request_id_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb4
-rw-r--r--actionpack/test/dispatch/response_test.rb97
-rw-r--r--actionpack/test/dispatch/routing/non_dispatch_routed_app_test.rb27
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb6
-rw-r--r--actionpack/test/dispatch/routing_test.rb127
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb1
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb9
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb34
-rw-r--r--actionpack/test/dispatch/static_test.rb1
-rw-r--r--actionpack/test/dispatch/system_testing/driver_test.rb82
-rw-r--r--actionpack/test/dispatch/system_testing/screenshot_helper_test.rb29
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb38
-rw-r--r--actionpack/test/dispatch/test_response_test.rb7
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb105
-rw-r--r--actionpack/test/journey/path/pattern_test.rb31
-rw-r--r--actionpack/test/journey/route/definition/scanner_test.rb1
-rw-r--r--actionpack/test/journey/route_test.rb41
-rw-r--r--actionpack/test/journey/router_test.rb7
190 files changed, 2846 insertions, 1688 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 90cf989100..9a6bd4bb45 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,166 +1,70 @@
-* Allow rescue from parameter parse errors:
+* Reduced log noise handling ActionController::RoutingErrors.
- ```
- rescue_from ActionDispatch::Http::Parameters::ParseError do
- head :unauthorized
- end
- ```
-
- *Gannon McGibbon*, *Josh Cheek*
-
-* Reset Capybara sessions if failed system test screenshot raising an exception.
-
- Reset Capybara sessions if `take_failed_screenshot` raise exception
- in system test `after_teardown`.
-
- *Maxim Perepelitsa*
-
-* Use request object for context if there's no controller
-
- There is no controller instance when using a redirect route or a
- mounted rack application so pass the request object as the context
- when resolving dynamic CSP sources in this scenario.
-
- Fixes #34200.
-
- *Andrew White*
-
-* Apply mapping to symbols returned from dynamic CSP sources
-
- Previously if a dynamic source returned a symbol such as :self it
- would be converted to a string implicity, e.g:
-
- policy.default_src -> { :self }
-
- would generate the header:
-
- Content-Security-Policy: default-src self
-
- and now it generates:
-
- Content-Security-Policy: default-src 'self'
-
- *Andrew White*
-
-* Add `ActionController::Parameters#each_value`.
-
- *Lukáš Zapletal*
-
-* Deprecate `ActionDispatch::Http::ParameterFilter` in favor of `ActiveSupport::ParameterFilter`.
-
- *Yoshiyuki Kinjo*
-
-* Remove undocumented `params` option from `url_for` helper.
-
- *Ilkka Oksanen*
+ *Alberto Fernández-Capel*
-* Encode Content-Disposition filenames on `send_data` and `send_file`.
- Previously, `send_data 'data', filename: "\u{3042}.txt"` sends
- `"filename=\"\u{3042}.txt\""` as Content-Disposition and it can be
- garbled.
- Now it follows [RFC 2231](https://tools.ietf.org/html/rfc2231) and
- [RFC 5987](https://tools.ietf.org/html/rfc5987) and sends
- `"filename=\"%3F.txt\"; filename*=UTF-8''%E3%81%82.txt"`.
- Most browsers can find filename correctly and old browsers fallback to ASCII
- converted name.
+* Add DSL for configuring HTTP Feature Policy
- *Fumiaki Matsushima*
+ 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:
-* Expose `ActionController::Parameters#each_key` which allows iterating over
- keys without allocating an array.
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
- *Richard Schneeman*
+ Example global policy
-* Purpose metadata for signed/encrypted cookies.
-
- Rails can now thwart attacks that attempt to copy signed/encrypted value
- of a cookie and use it as the value of another cookie.
-
- It does so by stashing the cookie-name in the purpose field which is
- then signed/encrypted along with the cookie value. Then, on a server-side
- read, we verify the cookie-names and discard any attacked cookies.
-
- Enable `action_dispatch.use_cookies_with_metadata` to use this feature, which
- writes cookies with the new purpose and expiry metadata embedded.
-
- *Assain Jaleel*
-
-* Raises `ActionController::RespondToMismatchError` with confliciting `respond_to` invocations.
-
- `respond_to` can match multiple types and lead to undefined behavior when
- multiple invocations are made and the types do not match:
-
- respond_to do |outer_type|
- outer_type.js do
- respond_to do |inner_type|
- inner_type.html { render body: "HTML" }
- end
- end
- end
-
- *Patrick Toomey*
-
-* `ActionDispatch::Http::UploadedFile` now delegates `to_path` to its tempfile.
-
- This allows uploaded file objects to be passed directly to `File.read`
- without raising a `TypeError`:
-
- uploaded_file = ActionDispatch::Http::UploadedFile.new(tempfile: tmp_file)
- File.read(uploaded_file)
-
- *Aaron Kromer*
-
-* Pass along arguments to underlying `get` method in `follow_redirect!`.
-
- Now all arguments passed to `follow_redirect!` are passed to the underlying
- `get` method. This for example allows to set custom headers for the
- redirection request to the server.
-
- follow_redirect!(params: { foo: :bar })
-
- *Remo Fritzsche*
-
-* Introduce a new error page to when the implicit render page is accessed in the browser.
-
- Now instead of showing an error page that with exception and backtraces we now show only
- one informative page.
+ ```
+ 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
+ ```
- *Vinicius Stock*
+ Example controller level policy
-* Introduce `ActionDispatch::DebugExceptions.register_interceptor`.
+ ```
+ class PagesController < ApplicationController
+ feature_policy do |p|
+ p.geolocation "https://example.com"
+ end
+ end
+ ```
- Exception aware plugin authors can use the newly introduced
- `.register_interceptor` method to get the processed exception, instead of
- monkey patching DebugExceptions.
+ *Jacob Bednarz*
- ActionDispatch::DebugExceptions.register_interceptor do |request, exception|
- HypoteticalPlugin.capture_exception(request, exception)
- end
+* Add the ability to set the CSP nonce only to the specified directives.
- *Genadi Samokovarov*
+ Fixes #35137.
-* Output only one Content-Security-Policy nonce header value per request.
+ *Yuji Yaginuma*
- Fixes #32597.
+* Keep part when scope option has value.
- *Andrey Novikov*, *Andrew White*
+ 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.
-* Move default headers configuration into their own module that can be included in controllers.
+ Fixes #33219.
- *Kevin Deisz*
+ *Alberto Almagro*
-* Add method `dig` to `session`.
+* Added `deep_transform_keys` and `deep_transform_keys!` methods to ActionController::Parameters.
- *claudiob*, *Takumi Shotoku*
+ *Gustavo Gutierrez*
-* Controller level `force_ssl` has been deprecated in favor of
- `config.force_ssl`.
+* Calling `ActionController::Parameters#transform_keys/!` without a block now returns
+ an enumerator for the parameters instead of the underlying hash.
- *Derek Prior*
+ *Eugene Kenny*
-* Rails 6 requires Ruby 2.4.1 or newer.
+* Fix strong parameters blocks all attributes even when only some keys are invalid (non-numerical).
+ It should only block invalid key's values instead.
- *Jeremy Daer*
+ *Stan Lo*
-Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 1cb3add0fc..ab7c27c209 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 David Heinemeier Hansson
+Copyright (c) 2004-2019 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index f56230ffa0..fe85bc5b7a 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -23,6 +23,7 @@ by default and Action View rendering is implicitly triggered by Action
Controller. However, these modules are designed to function on their own and
can be used outside of Rails.
+You can read more about Action Pack in the {Action Controller Overview}[https://guides.rubyonrails.org/action_controller_overview.html] guide.
== Download and installation
@@ -46,7 +47,7 @@ Action Pack is released under the MIT license:
API documentation is at:
-* http://api.rubyonrails.org
+* https://api.rubyonrails.org
Bug reports for the Ruby on Rails project can be filed here:
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index ec56de18f1..735eb734d0 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -9,13 +9,13 @@ Gem::Specification.new do |s|
s.summary = "Web-flow and rendering framework putting the VC in MVC (part of Rails)."
s.description = "Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server."
- s.required_ruby_version = ">= 2.4.1"
+ s.required_ruby_version = ">= 2.5.0"
s.license = "MIT"
s.author = "David Heinemeier Hansson"
s.email = "david@loudthinking.com"
- s.homepage = "http://rubyonrails.org"
+ s.homepage = "https://rubyonrails.org"
s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "lib/**/*"]
s.require_path = "lib"
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index 0477e7f1c9..d1ff62a032 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
require "action_pack"
+require "active_support"
require "active_support/rails"
require "active_support/i18n"
module AbstractController
extend ActiveSupport::Autoload
+ autoload :ActionNotFound, "abstract_controller/base"
autoload :Base
autoload :Caching
autoload :Callbacks
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/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb
index 95078a2a28..18677ddd18 100644
--- a/actionpack/lib/abstract_controller/caching/fragments.rb
+++ b/actionpack/lib/abstract_controller/caching/fragments.rb
@@ -28,7 +28,6 @@ module AbstractController
self.fragment_cache_keys = []
if respond_to?(:helper_method)
- helper_method :fragment_cache_key
helper_method :combined_fragment_cache_key
end
end
@@ -61,25 +60,6 @@ module AbstractController
end
# Given a key (as described in +expire_fragment+), returns
- # a key suitable for use in reading, writing, or expiring a
- # cached fragment. All keys begin with <tt>views/</tt>,
- # followed by any controller-wide key prefix values, ending
- # with the specified +key+ value. The key is expanded using
- # ActiveSupport::Cache.expand_cache_key.
- def fragment_cache_key(key)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Calling fragment_cache_key directly is deprecated and will be removed in Rails 6.0.
- All fragment accessors now use the combined_fragment_cache_key method that retains the key as an array,
- such that the caching stores can interrogate the parts for cache versions used in
- recyclable cache keys.
- MSG
-
- head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
- tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
- ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
- end
-
- # Given a key (as described in +expire_fragment+), returns
# a key array suitable for use in reading, writing, or expiring a
# cached fragment. All keys begin with <tt>:views</tt>,
# followed by <tt>ENV["RAILS_CACHE_ID"]</tt> or <tt>ENV["RAILS_APP_VERSION"]</tt> if set,
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/translation.rb b/actionpack/lib/abstract_controller/translation.rb
index 666e154e4c..4dad2a2b93 100644
--- a/actionpack/lib/abstract_controller/translation.rb
+++ b/actionpack/lib/abstract_controller/translation.rb
@@ -11,6 +11,7 @@ module AbstractController
# to translate many keys within the same controller / action and gives you a
# simple framework for scoping them consistently.
def translate(key, options = {})
+ options = options.dup
if key.to_s.first == "."
path = controller_path.tr("/", ".")
defaults = [:"#{path}#{key}"]
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/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index afbd38e7fe..d8b04d8ddb 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -56,7 +56,7 @@ module ActionController
def unpermitted_parameters(event)
debug do
unpermitted_keys = event.payload[:keys]
- "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}"
+ color("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}", RED)
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index f875aa5e6b..ec2207b8da 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -26,16 +26,15 @@ module ActionController
end
end
- def build(action, app = Proc.new)
+ def build(action, app = nil, &block)
action = action.to_s
- middlewares.reverse.inject(app) do |a, middleware|
+ middlewares.reverse.inject(app || block) do |a, middleware|
middleware.valid?(action) ? middleware.build(a) : a
end
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/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
index 2dc990f303..f9a758ff0e 100644
--- a/actionpack/lib/action_controller/metal/basic_implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
@@ -6,7 +6,7 @@ module ActionController
super.tap { default_render unless performed? }
end
- def default_render(*args)
+ def default_render
head :no_content
end
end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index d6911ee2b5..29d1919ec5 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/hash/keys"
-
module ActionController
module ConditionalGet
extend ActiveSupport::Concern
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/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
index 640c75536e..2f1544c69c 100644
--- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
+++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
@@ -51,7 +51,7 @@ module ActionController
end
def lookup_and_digest_template(template)
- ActionView::Digestor.digest name: template, finder: lookup_context
+ ActionView::Digestor.digest name: template, format: nil, finder: lookup_context
end
end
end
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 30034be018..e1e0c6f456 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -52,7 +52,7 @@ module ActionController
end
# Raised when a nested respond_to is triggered and the content types of each
- # are incompatible. For exampe:
+ # are incompatible. For example:
#
# respond_to do |outer_type|
# outer_type.js do
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/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 380f2e9591..a4861dc2c0 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -44,18 +44,18 @@ module ActionController #:nodoc:
end
private
- def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
+ def redirect_to(options = {}, response_options_and_flash = {}) #:doc:
self.class._flash_types.each do |flash_type|
- if type = response_status_and_flash.delete(flash_type)
+ if type = response_options_and_flash.delete(flash_type)
flash[flash_type] = type
end
end
- if other_flashes = response_status_and_flash.delete(:flash)
+ if other_flashes = response_options_and_flash.delete(:flash)
flash.update(other_flashes)
end
- super(options, response_status_and_flash)
+ super(options, response_options_and_flash)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 26e6f72b66..93fd57b640 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -40,7 +40,7 @@ module ActionController
protocol: "https://",
host: request.host,
path: request.fullpath,
- status: :moved_permanently
+ status: :moved_permanently,
}
if host_or_options.is_a?(Hash)
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 0faaac1ce4..193b488f6c 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -34,7 +34,7 @@ module ActionController
# end
# end
#
- # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
+ # Then, in any view rendered by <tt>EventsController</tt>, the <tt>format_time</tt> method can be called:
#
# <% @events.each do |event| -%>
# <p>
@@ -75,7 +75,7 @@ module ActionController
# Provides a proxy to access helper methods from outside the view.
def helpers
@helper_proxy ||= begin
- proxy = ActionView::Base.new
+ proxy = ActionView::Base.empty
proxy.config = config.inheritable_copy
proxy.extend(_helpers)
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 7036123d5d..6a274d35cb 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -69,21 +69,20 @@ module ActionController
extend ActiveSupport::Concern
module ClassMethods
- def http_basic_authenticate_with(options = {})
- before_action(options.except(:name, :password, :realm)) do
- authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
- # This comparison uses & so that it doesn't short circuit and
- # uses `secure_compare` so that length information
- # isn't leaked.
- ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
- ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
- end
- end
+ def http_basic_authenticate_with(name:, password:, realm: nil, **options)
+ before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
+ end
+ end
+
+ def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
+ authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
+ ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
+ ActiveSupport::SecurityUtils.secure_compare(given_password, password)
end
end
- def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure)
- authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message)
+ def authenticate_or_request_with_http_basic(realm = nil, message = nil, &login_procedure)
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm || "Application", message)
end
def authenticate_with_http_basic(&login_procedure)
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index d3bb58f48b..8365ddca57 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -30,9 +30,9 @@ module ActionController
# :stopdoc:
include BasicImplicitRender
- def default_render(*args)
+ def default_render
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
- render(*args)
+ render
elsif any_templates?(action_name.to_s, _prefixes)
message = "#{self.class.name}\##{action_name} is missing a template " \
"for this request format and variant.\n" \
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index be9449629f..6f7fc0d624 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -30,13 +30,11 @@ module ActionController
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
- begin
- result = super
+ super.tap do
payload[:status] = response.status
- result
- ensure
- append_info_to_payload(payload)
end
+ ensure
+ append_info_to_payload(payload)
end
end
@@ -71,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 083b762f5a..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
@@ -146,7 +145,7 @@ module ActionController
def write(string)
unless @response.committed?
- @response.set_header "Cache-Control", "no-cache"
+ @response.headers["Cache-Control"] ||= "no-cache"
@response.delete_header "Content-Length"
end
@@ -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
@@ -305,7 +301,7 @@ module ActionController
logger.fatal do
message = +"\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
message << " " << exception.backtrace.join("\n ")
"#{message}\n\n"
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 118da11990..5c6f7fe396 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -124,6 +124,14 @@ module ActionController #:nodoc:
#
# render json: @people
#
+ # +any+ can also be used with no arguments, in which case it will be used for any format requested by
+ # the user:
+ #
+ # respond_to do |format|
+ # format.html
+ # format.any { redirect_to support_path }
+ # end
+ #
# Formats can have different variants.
#
# The request variant is a specialization of the request format, like <tt>:tablet</tt>,
@@ -197,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 7361946de5..150ae2666c 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -93,7 +93,7 @@ module ActionController
end
def model
- super || synchronize { super || self.model = _default_wrap_model }
+ super || self.model = _default_wrap_model
end
def include
@@ -241,26 +241,11 @@ module ActionController
# Performs parameters wrapping upon the request. Called automatically
# by the metal call stack.
def process_action(*args)
- if _wrapper_enabled?
- wrapped_hash = _wrap_parameters request.request_parameters
- wrapped_keys = request.request_parameters.keys
- wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
-
- # This will make the wrapped hash accessible from controller and view.
- request.parameters.merge! wrapped_hash
- request.request_parameters.merge! wrapped_hash
-
- # This will display the wrapped hash in the log file.
- request.filtered_parameters.merge! wrapped_filtered_hash
- end
- ensure
- # NOTE: Rescues all exceptions so they
- # may be caught in ActionController::Rescue.
- return super
+ _perform_parameter_wrapping if _wrapper_enabled?
+ super
end
private
-
# Returns the wrapper key which will be used to store wrapped parameters.
def _wrapper_key
_wrapper_options.name
@@ -292,5 +277,20 @@ module ActionController
ref = request.content_mime_type.ref
_wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
end
+
+ def _perform_parameter_wrapping
+ wrapped_hash = _wrap_parameters request.request_parameters
+ wrapped_keys = request.request_parameters.keys
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
+
+ # This will make the wrapped hash accessible from controller and view.
+ request.parameters.merge! wrapped_hash
+ request.request_parameters.merge! wrapped_hash
+
+ # This will display the wrapped hash in the log file.
+ request.filtered_parameters.merge! wrapped_filtered_hash
+ rescue ActionDispatch::Http::Parameters::ParseError
+ # swallow parse error exception
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 2804a06a58..67c198d150 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -55,11 +55,11 @@ module ActionController
# Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
# To terminate the execution of the function immediately after the +redirect_to+, use return.
# redirect_to post_url(@post) and return
- def redirect_to(options = {}, response_status = {})
+ def redirect_to(options = {}, response_options = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
- self.status = _extract_redirect_to_status(options, response_status)
+ self.status = _extract_redirect_to_status(options, response_options)
self.location = _compute_redirect_to_location(request, options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
end
@@ -114,11 +114,11 @@ module ActionController
public :_compute_redirect_to_location
private
- def _extract_redirect_to_status(options, response_status)
+ def _extract_redirect_to_status(options, response_options)
if options.is_a?(Hash) && options.key?(:status)
Rack::Utils.status_code(options.delete(:status))
- elsif response_status.key?(:status)
- Rack::Utils.status_code(response_status[:status])
+ elsif response_options.key?(:status)
+ Rack::Utils.status_code(response_options[:status])
else
302
end
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..efa5de313c 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,7 +72,7 @@ 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
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index cb109c6ad8..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)
@@ -431,7 +429,7 @@ module ActionController #:nodoc:
The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
means you have the 'no-referrer' Referrer-Policy header enabled, or that the request came from a site that
refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
- best solution is to change your referrer policy to something less strict like same-origin or strict-same-origin.
+ best solution is to change your referrer policy to something less strict like same-origin or strict-origin.
If you cannot change the referrer policy, you can disable origin checking with the
Rails.application.config.action_controller.forgery_protection_origin_check setting.
MSG
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 04922b0715..6fbd52dd51 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -4,7 +4,6 @@ require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/array/wrap"
require "active_support/core_ext/string/filters"
require "active_support/core_ext/object/to_query"
-require "active_support/rescuable"
require "action_dispatch/http/upload"
require "rack/test"
require "stringio"
@@ -224,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>.
@@ -254,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.
@@ -674,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
@@ -795,7 +820,7 @@ module ActionController
@permitted = coder.map["ivars"][:@permitted]
when "!ruby/object:ActionController::Parameters"
# YAML's Object format. Only needed because of the format
- # backwardscompability above, otherwise equivalent to YAML's initialization.
+ # backwards compatibility above, otherwise equivalent to YAML's initialization.
@parameters, @permitted = coder.map["parameters"], coder.map["permitted"]
end
end
@@ -812,8 +837,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
@@ -858,15 +889,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
@@ -1092,9 +1121,6 @@ module ActionController
# See ActionController::Parameters.require and ActionController::Parameters.permit
# for more information.
module StrongParameters
- extend ActiveSupport::Concern
- include ActiveSupport::Rescuable
-
# Returns a new ActionController::Parameters object that
# has been instantiated with the <tt>request.parameters</tt>.
def params
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index 2b4559c760..dadf6d3445 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/hash/keys"
-
module ActionController
# ActionController::Renderer allows you to render arbitrary templates
# without requirement of being in controller actions.
@@ -76,7 +74,7 @@ module ActionController
# * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt> for details.
# * <tt>:file</tt> - Renders an explicit template file. Add <tt>:locals</tt> to pass in, if so desired.
# It shouldn’t be used directly with unsanitized user input due to lack of validation.
- # * <tt>:inline</tt> - Renders a ERB template string.
+ # * <tt>:inline</tt> - Renders an ERB template string.
# * <tt>:plain</tt> - Renders provided text and sets the content type as <tt>text/plain</tt>.
# * <tt>:html</tt> - Renders the provided HTML safe string, otherwise
# performs HTML escape on the string first. Sets the content type as <tt>text/html</tt>.
@@ -118,7 +116,7 @@ module ActionController
RACK_VALUE_TRANSLATION = {
https: ->(v) { v ? "on" : "off" },
- method: ->(v) { v.upcase },
+ method: ->(v) { -v.upcase },
}
def rack_key_for(key)
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 5d784ceb31..47e0099f20 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -26,7 +26,7 @@ module ActionController
end
end
- # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
+ # ActionController::TestCase will be deprecated and moved to a gem in the future.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
@@ -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
@@ -276,9 +274,6 @@ module ActionController
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
#
- # (Earlier versions of \Rails required each functional test to subclass
- # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
- #
# == Controller is automatically inferred
#
# ActionController::TestCase will automatically infer the controller under test
@@ -457,7 +452,7 @@ module ActionController
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
- def process(action, method: "GET", params: {}, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
+ def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
check_required_ivars
http_method = method.to_s.upcase
@@ -485,7 +480,7 @@ module ActionController
format ||= as
end
- parameters = params.symbolize_keys
+ parameters = (params || {}).symbolize_keys
if format
parameters[:format] = format
@@ -597,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 0822cdc0a6..67d303a368 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -43,17 +43,21 @@ module ActionDispatch
eager_autoload do
autoload_under "http" do
autoload :ContentSecurityPolicy
+ autoload :FeaturePolicy
autoload :Request
autoload :Response
end
end
autoload_under "middleware" do
+ autoload :HostAuthorization
autoload :RequestId
autoload :Callbacks
autoload :Cookies
+ autoload :ActionableExceptions
autoload :DebugExceptions
autoload :DebugLocks
+ autoload :DebugView
autoload :ExceptionWrapper
autoload :Executor
autoload :Flash
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index f67b13f657..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])
@@ -197,10 +196,12 @@ module ActionDispatch
if control.empty?
# Let middleware handle default behavior
elsif control[:no_cache]
- self._cache_control = NO_CACHE
- if control[:extras]
- self._cache_control = _cache_control + ", #{control[:extras].join(', ')}"
- end
+ options = []
+ options << PUBLIC if control[:public]
+ options << NO_CACHE
+ options.concat(control[:extras]) if control[:extras]
+
+ self._cache_control = options.join(", ")
else
extras = control[:extras]
max_age = control[:max_age]
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 498b1e6695..a2cac49082 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -79,6 +79,11 @@ module ActionDispatch
else
[Mime[:html]]
end
+
+ v = v.select do |format|
+ format.symbol || format.ref == "*/*"
+ end
+
set_header k, v
end
end
@@ -149,7 +154,6 @@ module ActionDispatch
end
private
-
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
def valid_accept_header # :doc:
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index c3e0ea3c89..ed1d50f3b9 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -170,6 +170,7 @@ module Mime
def parse(accept_header)
if !accept_header.include?(",")
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
+ return [] unless accept_header
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
else
list, index = [], 0
@@ -221,7 +222,18 @@ module Mime
attr_reader :hash
+ MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
+ MIME_PARAMETER_KEY = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
+ MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}#{Regexp.escape('"')}?"
+ MIME_PARAMETER = "\s*\;\s+#{MIME_PARAMETER_KEY}(?:\=#{MIME_PARAMETER_VALUE})?"
+ MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?:\s*#{MIME_PARAMETER}\s*)*)\z/
+
+ class InvalidMimeType < StandardError; end
+
def initialize(string, symbol = nil, synonyms = [])
+ unless MIME_REGEXP.match?(string)
+ raise InvalidMimeType, "#{string.inspect} is not a valid MIME type"
+ end
@symbol, @synonyms = symbol, synonyms
@string = string
@hash = [@string, @synonyms, @symbol].hash
@@ -278,11 +290,9 @@ module Mime
def all?; false; end
protected
-
attr_reader :string, :synonyms
private
-
def to_ary; end
def to_a; end
@@ -303,7 +313,7 @@ module Mime
include Singleton
def initialize
- super "*/*", :all
+ super "*/*", nil
end
def all?; true; 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 1d38942a31..ea3692951f 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -82,9 +82,11 @@ module ActionDispatch # :nodoc:
SET_COOKIE = "Set-Cookie"
LOCATION = "Location"
NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
+ CONTENT_TYPE_PARSER = /\A(?<type>[^;\s]+)?(?:.*;\s*charset=(?<quote>"?)(?<charset>[^;\s]+)\k<quote>)?/ # :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.
@@ -142,7 +144,6 @@ module ActionDispatch # :nodoc:
end
private
-
def each_chunk(&block)
@buf.each(&block)
end
@@ -242,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
@@ -404,15 +419,12 @@ module ActionDispatch # :nodoc:
end
private
-
ContentTypeHeader = Struct.new :mime_type, :charset
NullContentTypeHeader = ContentTypeHeader.new nil, nil
def parse_content_type(content_type)
- if content_type
- type, charset = content_type.split(/;\s*charset=/)
- type = nil if type && type.empty?
- ContentTypeHeader.new(type, charset)
+ if content_type && match = CONTENT_TYPE_PARSER.match(content_type)
+ ContentTypeHeader.new(match[:type], match[:charset])
else
NullContentTypeHeader
end
@@ -459,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/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 827f022ca2..0da8f5c14e 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -20,7 +20,6 @@ module ActionDispatch
# A +Tempfile+ object with the actual uploaded file. Note that some of
# its interface is available directly.
attr_accessor :tempfile
- alias :to_io :tempfile
# A string with the headers of the multipart request.
attr_accessor :headers
@@ -84,6 +83,10 @@ module ActionDispatch
def eof?
@tempfile.eof?
end
+
+ def to_io
+ @tempfile.to_io
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 3af4c176a7..3b0f6378ea 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -78,109 +78,108 @@ module ActionDispatch
end
private
-
- def add_params(path, params)
- params = { params: params } unless params.is_a?(Hash)
- params.reject! { |_, v| v.to_param.nil? }
- query = params.to_query
- path << "?#{query}" unless query.empty?
- end
-
- def add_anchor(path, anchor)
- if anchor
- path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}"
+ def add_params(path, params)
+ params = { params: params } unless params.is_a?(Hash)
+ params.reject! { |_, v| v.to_param.nil? }
+ query = params.to_query
+ path << "?#{query}" unless query.empty?
end
- end
- def extract_domain_from(host, tld_length)
- host.split(".").last(1 + tld_length).join(".")
- end
+ def add_anchor(path, anchor)
+ if anchor
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}"
+ end
+ end
- def extract_subdomains_from(host, tld_length)
- parts = host.split(".")
- parts[0..-(tld_length + 2)]
- end
+ def extract_domain_from(host, tld_length)
+ host.split(".").last(1 + tld_length).join(".")
+ end
- def add_trailing_slash(path)
- if path.include?("?")
- path.sub!(/\?/, '/\&')
- elsif !path.include?(".")
- path.sub!(/[^\/]\z|\A\z/, '\&/')
+ def extract_subdomains_from(host, tld_length)
+ parts = host.split(".")
+ parts[0..-(tld_length + 2)]
end
- end
- def build_host_url(host, port, protocol, options, path)
- if match = host.match(HOST_REGEXP)
- protocol ||= match[1] unless protocol == false
- host = match[2]
- port = match[3] unless options.key? :port
+ def add_trailing_slash(path)
+ if path.include?("?")
+ path.sub!(/\?/, '/\&')
+ elsif !path.include?(".")
+ path.sub!(/[^\/]\z|\A\z/, '\&/')
+ end
end
- protocol = normalize_protocol protocol
- host = normalize_host(host, options)
+ def build_host_url(host, port, protocol, options, path)
+ if match = host.match(HOST_REGEXP)
+ protocol ||= match[1] unless protocol == false
+ host = match[2]
+ port = match[3] unless options.key? :port
+ end
- result = protocol.dup
+ protocol = normalize_protocol protocol
+ host = normalize_host(host, options)
- if options[:user] && options[:password]
- result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
- end
+ result = protocol.dup
- result << host
- normalize_port(port, protocol) { |normalized_port|
- result << ":#{normalized_port}"
- }
+ if options[:user] && options[:password]
+ result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
+ end
- result.concat path
- end
+ result << host
+ normalize_port(port, protocol) { |normalized_port|
+ result << ":#{normalized_port}"
+ }
- def named_host?(host)
- IP_HOST_REGEXP !~ host
- end
+ result.concat path
+ end
- def normalize_protocol(protocol)
- case protocol
- when nil
- "http://"
- when false, "//"
- "//"
- when PROTOCOL_REGEXP
- "#{$1}://"
- else
- raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}"
+ def named_host?(host)
+ IP_HOST_REGEXP !~ host
end
- end
- def normalize_host(_host, options)
- return _host unless named_host?(_host)
+ def normalize_protocol(protocol)
+ case protocol
+ when nil
+ "http://"
+ when false, "//"
+ "//"
+ when PROTOCOL_REGEXP
+ "#{$1}://"
+ else
+ raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}"
+ end
+ end
+
+ def normalize_host(_host, options)
+ return _host unless named_host?(_host)
- tld_length = options[:tld_length] || @@tld_length
- subdomain = options.fetch :subdomain, true
- domain = options[:domain]
+ tld_length = options[:tld_length] || @@tld_length
+ subdomain = options.fetch :subdomain, true
+ domain = options[:domain]
- host = +""
- if subdomain == true
- return _host if domain.nil?
+ host = +""
+ if subdomain == true
+ return _host if domain.nil?
- host << extract_subdomains_from(_host, tld_length).join(".")
- elsif subdomain
- host << subdomain.to_param
+ host << extract_subdomains_from(_host, tld_length).join(".")
+ elsif subdomain
+ host << subdomain.to_param
+ end
+ host << "." unless host.empty?
+ host << (domain || extract_domain_from(_host, tld_length))
+ host
end
- host << "." unless host.empty?
- host << (domain || extract_domain_from(_host, tld_length))
- host
- end
- def normalize_port(port, protocol)
- return unless port
+ def normalize_port(port, protocol)
+ return unless port
- case protocol
- when "//" then yield port
- when "https://"
- yield port unless port.to_i == 443
- else
- yield port unless port.to_i == 80
+ case protocol
+ when "//" then yield port
+ when "https://"
+ yield port unless port.to_i == 443
+ else
+ yield port unless port.to_i == 80
+ end
end
- end
end
def initialize
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/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index 32f632800c..086d6a3e07 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -65,12 +65,12 @@ module ActionDispatch
def literal?; false; end
end
- %w{ Symbol Slash Dot }.each do |t|
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
- class #{t} < Terminal;
- def type; :#{t.upcase}; end
- end
- eoruby
+ class Slash < Terminal # :nodoc:
+ def type; :SLASH; end
+ end
+
+ class Dot < Terminal # :nodoc:
+ def type; :DOT; end
end
class Symbol < Terminal # :nodoc:
@@ -89,6 +89,7 @@ module ActionDispatch
regexp == DEFAULT_EXP
end
+ def type; :SYMBOL; end
def symbol?; true; end
end
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 537f479ee5..e4ba82ebdd 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -119,7 +119,8 @@ module ActionDispatch
class UnanchoredRegexp < AnchoredRegexp # :nodoc:
def accept(node)
- %r{\A#{visit node}}
+ path = visit node
+ path == "/" ? %r{\A/} : %r{\A#{path}(?:\b|\Z|/)}
end
end
@@ -136,6 +137,10 @@ module ActionDispatch
Array.new(length - 1) { |i| self[i + 1] }
end
+ def named_captures
+ @names.zip(captures).to_h
+ end
+
def [](x)
idx = @offsets[x - 1] + x
@match[idx]
@@ -169,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..4aee7a6f83 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
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 c0377459d5..3f055db66d 100644
--- a/actionpack/lib/action_dispatch/journey/routes.rb
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -56,7 +56,6 @@ module ActionDispatch
end
def simulator
- return if ast.nil?
@simulator ||= begin
gtg = GTG::Builder.new(ast).transition_table
GTG::Simulator.new(gtg)
@@ -72,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/actionable_exceptions.rb b/actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb
new file mode 100644
index 0000000000..e94cc46603
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require "erb"
+require "action_dispatch/http/request"
+require "active_support/actionable_error"
+
+module ActionDispatch
+ class ActionableExceptions # :nodoc:
+ cattr_accessor :endpoint, default: "/rails/actions"
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ request = ActionDispatch::Request.new(env)
+ return @app.call(env) unless actionable_request?(request)
+
+ ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
+
+ redirect_to request.params[:location]
+ end
+
+ private
+ def actionable_request?(request)
+ request.show_exceptions? && request.post? && request.path == endpoint
+ end
+
+ def redirect_to(location)
+ body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
+
+ [302, {
+ "Content-Type" => "text/html; charset=#{Response.default_charset}",
+ "Content-Length" => body.bytesize.to_s,
+ "Location" => location,
+ }, [body]]
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 5b2ad36dd5..87fe19225b 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -24,10 +24,8 @@ module ActionDispatch
def call(env)
error = nil
result = run_callbacks :call do
- begin
- @app.call(env)
- rescue => error
- end
+ @app.call(env)
+ rescue => error
end
raise error if error
result
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 26d3fd936f..642f155085 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -61,10 +61,6 @@ module ActionDispatch
get_header Cookies::SIGNED_COOKIE_DIGEST
end
- def secret_token
- get_header Cookies::SECRET_TOKEN
- end
-
def secret_key_base
get_header Cookies::SECRET_KEY_BASE
end
@@ -181,7 +177,6 @@ module ActionDispatch
USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
- SECRET_TOKEN = "action_dispatch.secret_token"
SECRET_KEY_BASE = "action_dispatch.secret_key_base"
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
COOKIES_DIGEST = "action_dispatch.cookies_digest"
@@ -215,9 +210,6 @@ module ActionDispatch
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
# cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
- # legacy cookies signed with the old key generator will be transparently upgraded.
- #
# This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
#
# Example:
@@ -233,9 +225,6 @@ module ActionDispatch
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +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.
#
@@ -263,11 +252,6 @@ module ActionDispatch
end
private
-
- 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.encrypted_signed_cookie_salt.present? &&
@@ -302,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
@@ -353,7 +337,7 @@ module ActionDispatch
def update_cookies_from_jar
request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
- set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
+ set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
@cookies.update set_cookies if set_cookies
end
@@ -443,7 +427,6 @@ module ActionDispatch
mattr_accessor :always_write_cookie, default: false
private
-
def escape(string)
::Rack::Utils.escape(string)
end
@@ -503,13 +486,8 @@ module ActionDispatch
end
def cookie_metadata(name, options)
- if request.use_cookies_with_metadata
- metadata = expiry_options(options)
- metadata[:purpose] = "cookie.#{name}"
-
- metadata
- else
- {}
+ expiry_options(options).tap do |metadata|
+ metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata
end
end
@@ -592,10 +570,6 @@ module ActionDispatch
request.cookies_rotations.signed.each do |*secrets, **options|
@verifier.rotate(*secrets, serializer: SERIALIZER, **options)
end
-
- if upgrade_legacy_signed_cookies?
- @verifier.rotate request.secret_token, serializer: SERIALIZER
- end
end
private
@@ -640,10 +614,6 @@ module ActionDispatch
@encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
end
-
- if upgrade_legacy_signed_cookies?
- @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
- end
end
private
@@ -652,7 +622,7 @@ module ActionDispatch
@encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
end
rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
- parse_legacy_signed_message(name, encrypted_message)
+ nil
end
def commit(name, options)
@@ -660,16 +630,6 @@ module ActionDispatch
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
-
- def parse_legacy_signed_message(name, legacy_signed_message)
- if defined?(@legacy_verifier)
- deserialize(name) do |rotate|
- rotate.call
-
- @legacy_verifier.verified(legacy_signed_message)
- end
- end
- end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 5f5fdbc66a..e546d1c11f 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -3,53 +3,14 @@
require "action_dispatch/http/request"
require "action_dispatch/middleware/exception_wrapper"
require "action_dispatch/routing/inspector"
+
require "action_view"
require "action_view/base"
-require "pp"
-
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", __dir__)
-
- class DebugView < ActionView::Base
- def debug_params(params)
- clean_params = params.clone
- clean_params.delete("action")
- clean_params.delete("controller")
-
- if clean_params.empty?
- "None"
- else
- PP.pp(clean_params, +"", 200)
- end
- end
-
- def debug_headers(headers)
- if headers.present?
- headers.inspect.gsub(",", ",\n")
- else
- "None"
- end
- end
-
- def debug_hash(object)
- object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
- end
-
- def render(*)
- logger = ActionView::Base.logger
-
- if logger && logger.respond_to?(:silence)
- logger.silence { super }
- else
- super
- end
- end
- end
-
cattr_reader :interceptors, instance_accessor: false, default: []
def self.register_interceptor(object = nil, &block)
@@ -81,17 +42,14 @@ module ActionDispatch
end
private
-
def invoke_interceptors(request, exception)
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
@interceptors.each do |interceptor|
- begin
- interceptor.call(request, exception)
- rescue Exception
- log_error(request, wrapper)
- end
+ interceptor.call(request, exception)
+ rescue Exception
+ log_error(request, wrapper)
end
end
@@ -101,7 +59,11 @@ module ActionDispatch
log_error(request, wrapper)
if request.get_header("action_dispatch.show_detailed_exceptions")
- content_type = request.formats.first
+ begin
+ content_type = request.formats.first
+ rescue Mime::Type::InvalidMimeType
+ render_for_api_request(Mime[:text], wrapper)
+ end
if api_request?(content_type)
render_for_api_request(content_type, wrapper)
@@ -152,7 +114,7 @@ module ActionDispatch
end
def create_template(request, wrapper)
- DebugView.new([RESCUES_TEMPLATE_PATH],
+ DebugView.new(
request: request,
exception_wrapper: wrapper,
exception: wrapper.exception,
@@ -175,16 +137,17 @@ 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
- logger.fatal " "
- logger.fatal "#{exception.class} (#{exception.message}):"
- log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
- logger.fatal " "
- log_array logger, trace
+ message = []
+ message << " "
+ message << "#{exception.class} (#{exception.message}):"
+ message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
+ message << " "
+ message.concat(trace)
+
+ log_array(logger, message)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_view.rb b/actionpack/lib/action_dispatch/middleware/debug_view.rb
new file mode 100644
index 0000000000..148662a48b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/debug_view.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require "pp"
+
+require "action_view"
+require "action_view/base"
+
+module ActionDispatch
+ class DebugView < ActionView::Base # :nodoc:
+ RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
+
+ def initialize(assigns)
+ paths = [RESCUES_TEMPLATE_PATH]
+ lookup_context = ActionView::LookupContext.new(paths)
+ super(lookup_context, assigns)
+ end
+
+ def compiled_method_container
+ self.class
+ end
+
+ def debug_params(params)
+ clean_params = params.clone
+ clean_params.delete("action")
+ clean_params.delete("controller")
+
+ if clean_params.empty?
+ "None"
+ else
+ PP.pp(clean_params, +"", 200)
+ end
+ end
+
+ def debug_headers(headers)
+ if headers.present?
+ headers.inspect.gsub(",", ",\n")
+ else
+ "None"
+ end
+ end
+
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ end
+
+ def render(*)
+ logger = ActionView::Base.logger
+
+ if logger && logger.respond_to?(:silence)
+ logger.silence { super }
+ else
+ super
+ end
+ end
+
+ 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 fb2b2bd3b0..e4a2a51c57 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -12,6 +12,7 @@ module ActionDispatch
"ActionController::UnknownHttpMethod" => :method_not_allowed,
"ActionController::NotImplemented" => :not_implemented,
"ActionController::UnknownFormat" => :not_acceptable,
+ "Mime::Type::InvalidMimeType" => :not_acceptable,
"ActionController::MissingExactTemplate" => :not_acceptable,
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
@@ -31,22 +32,45 @@ module ActionDispatch
"ActionController::MissingExactTemplate" => "missing_exact_template",
)
+ cattr_accessor :wrapper_exceptions, default: [
+ "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 = original_exception(exception)
+ @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_name)
+ exception.cause
+ else
+ exception
+ end
+ end
+
def rescue_template
- @@rescue_templates[@exception.class.name]
+ @@rescue_templates[@exception_class_name]
end
def status_code
- self.class.status_code_for_exception(@exception.class.name)
+ 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
@@ -117,19 +141,10 @@ module ActionDispatch
end
private
-
def backtrace
Array(@exception.backtrace)
end
- def original_exception(exception)
- if @@rescue_responses.has_key?(exception.cause.class.name)
- exception.cause
- else
- exception
- end
- end
-
def causes_for(exception)
return enum_for(__method__, exception) unless block_given?
diff --git a/actionpack/lib/action_dispatch/middleware/host_authorization.rb b/actionpack/lib/action_dispatch/middleware/host_authorization.rb
new file mode 100644
index 0000000000..de7739b9b6
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/host_authorization.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require "action_dispatch/http/request"
+
+module ActionDispatch
+ # This middleware guards from DNS rebinding attacks by explicitly permitting
+ # the hosts a request can be sent to.
+ #
+ # When a request comes to an unauthorized host, the +response_app+
+ # application will be executed and rendered. If no +response_app+ is given, a
+ # default one will run, which responds with +403 Forbidden+.
+ class HostAuthorization
+ class Permissions # :nodoc:
+ def initialize(hosts)
+ @hosts = sanitize_hosts(hosts)
+ end
+
+ def empty?
+ @hosts.empty?
+ end
+
+ def allows?(host)
+ @hosts.any? do |allowed|
+ allowed === host
+ rescue
+ # IPAddr#=== raises an error if you give it a hostname instead of
+ # IP. Treat similar errors as blocked access.
+ false
+ end
+ end
+
+ private
+ def sanitize_hosts(hosts)
+ Array(hosts).map do |host|
+ case host
+ when Regexp then sanitize_regexp(host)
+ when String then sanitize_string(host)
+ else host
+ end
+ end
+ end
+
+ def sanitize_regexp(host)
+ /\A#{host}\z/
+ end
+
+ def sanitize_string(host)
+ if host.start_with?(".")
+ /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/
+ else
+ host
+ end
+ end
+ end
+
+ DEFAULT_RESPONSE_APP = -> env do
+ request = Request.new(env)
+
+ format = request.xhr? ? "text/plain" : "text/html"
+ template = DebugView.new(host: request.host)
+ body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
+
+ [403, {
+ "Content-Type" => "#{format}; charset=#{Response.default_charset}",
+ "Content-Length" => body.bytesize.to_s,
+ }, [body]]
+ end
+
+ def initialize(app, hosts, response_app = nil)
+ @app = app
+ @permissions = Permissions.new(hosts)
+ @response_app = response_app || DEFAULT_RESPONSE_APP
+ end
+
+ def call(env)
+ return @app.call(env) if @permissions.empty?
+
+ request = Request.new(env)
+
+ if authorized?(request)
+ mark_as_authorized(request)
+ @app.call(env)
+ else
+ @response_app.call(env)
+ end
+ 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/, "")
+
+ @permissions.allows?(origin_host) &&
+ (forwarded_host.blank? || @permissions.allows?(forwarded_host))
+ end
+
+ def mark_as_authorized(request)
+ request.set_header("action_dispatch.authorized_host", request.host)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 3feb3a19f3..3a2a1d7334 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -21,14 +21,17 @@ module ActionDispatch
def call(env)
request = ActionDispatch::Request.new(env)
status = request.path_info[1..-1].to_i
- content_type = request.formats.first
- body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
+ begin
+ content_type = request.formats.first
+ rescue Mime::Type::InvalidMimeType
+ content_type = Mime[:text]
+ end
+ body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
render(status, content_type, body)
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 35158f9062..c5d4a0bd31 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -156,20 +156,17 @@ module ActionDispatch
end
private
-
def ips_from(header) # :doc:
return [] unless header
# Split the comma-separated list into an array of strings.
ips = header.strip.split(/[,\s]+/)
ips.select do |ip|
- begin
- # Only return IPs that are valid according to the IPAddr#new method.
- range = IPAddr.new(ip).to_range
- # We want to make sure nobody is sneaking a netmask in.
- range.begin == range.end
- rescue ArgumentError
- nil
- end
+ # Only return IPs that are valid according to the IPAddr#new method.
+ range = IPAddr.new(ip).to_range
+ # We want to make sure nobody is sneaking a netmask in.
+ range.begin == range.end
+ rescue ArgumentError
+ nil
end
end
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 df680c1c5f..892d88803e 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -16,11 +16,6 @@ module ActionDispatch
# The cookie jar used for storage is automatically configured to be the
# best possible option given your application's configuration.
#
- # If you only have secret_token set, your cookies will be signed, but
- # not encrypted. This means a user cannot alter their +user_id+ without
- # knowing your app's secret key, but can easily read their +user_id+. This
- # was the default for Rails 3 apps.
- #
# Your cookies will be encrypted using your apps secret_key_base. This
# goes a step further than signed cookies in that encrypted cookies cannot
# be altered or read by users. This is the default starting in Rails 4.
@@ -29,9 +24,10 @@ module ActionDispatch
#
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
#
- # By default, your secret key base is derived from your application name in
- # the test and development environments. In all other environments, it is stored
- # encrypted in the <tt>config/credentials.yml.enc</tt> file.
+ # In the development and test environments your application's secret key base is
+ # generated by Rails and stored in a temporary file in <tt>tmp/development_secret.txt</tt>.
+ # In all other environments, it is stored encrypted in the
+ # <tt>config/credentials.yml.enc</tt> file.
#
# If your application was not updated to Rails 5.2 defaults, the secret_key_base
# will be found in the old <tt>config/secrets.yml</tt> file.
@@ -71,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 3c88afd4d3..a35c0da3d9 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -40,12 +40,11 @@ module ActionDispatch
end
private
-
def render_exception(request, exception)
backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
status = wrapper.status_code
- request.set_header "action_dispatch.exception", wrapper.exception
+ request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
request.set_header "action_dispatch.original_path", request.path_info
request.path_info = "/#{status}"
response = @exceptions_app.call(request.env)
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index b82f8aa3a3..775110d95e 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -36,6 +36,31 @@ module ActionDispatch
def build(app)
klass.new(app, *args, &block)
end
+
+ def build_instrumented(app)
+ InstrumentationProxy.new(build(app), inspect)
+ end
+ end
+
+ # This class is used to instrument the execution of a single middleware.
+ # It proxies the `call` method transparently and instruments the method
+ # call.
+ class InstrumentationProxy
+ EVENT_NAME = "process_middleware.action_dispatch"
+
+ def initialize(middleware, class_name)
+ @middleware = middleware
+
+ @payload = {
+ middleware: class_name,
+ }
+ end
+
+ def call(env)
+ ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do
+ @middleware.call(env)
+ end
+ end
end
include Enumerable
@@ -97,12 +122,18 @@ module ActionDispatch
middlewares.push(build_middleware(klass, args, block))
end
- def build(app = Proc.new)
- middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
+ def build(app = nil, &block)
+ 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/_actions.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb
new file mode 100644
index 0000000000..b6c6d2f50d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb
@@ -0,0 +1,13 @@
+<% actions = ActiveSupport::ActionableError.actions(exception) %>
+
+<% if actions.any? %>
+ <div class="actions">
+ <% actions.each do |action, _| %>
+ <%= button_to action, ActionDispatch::ActionableExceptions.endpoint, params: {
+ error: exception.class.name,
+ action: action,
+ location: request.path
+ } %>
+ <% end %>
+ </div>
+<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb
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/blocked_host.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb
new file mode 100644
index 0000000000..1fbc107e28
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb
@@ -0,0 +1,7 @@
+<header>
+ <h1>Blocked host: <%= @host %></h1>
+</header>
+<div id="container">
+ <h2>To allow requests to <%= @host %>, add the following to your environment configuration:</h2>
+ <pre>config.hosts &lt;&lt; "<%= @host %>"</pre>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb
new file mode 100644
index 0000000000..a94dd982a7
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb
@@ -0,0 +1,5 @@
+Blocked host: <%= @host %>
+
+To allow requests to <%= @host %>, add the following to your environment configuration:
+
+ config.hosts << "<%= @host %>"
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 bde26f46c2..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,14 +1,18 @@
<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>
</header>
<div id="container">
- <h2><%= h @exception.message %></h2>
+ <h2>
+ <%= h @exception.message %>
+
+ <%= render "rescues/actions", exception: @exception, request: @request %>
+ </h2>
<%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx, error_index: 0 %>
<%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show, error_index: 0 %>
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/invalid_statement.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
index e8454acfad..77cfdd20c8 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
@@ -10,9 +10,12 @@
<div id="container">
<h2>
<%= h @exception.message %>
- <% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
+ <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
<br />To resolve this issue run: rails active_storage:install
<% end %>
+ <% if defined?(ActionMailbox) && @exception.message.match?(%r{#{ActionMailbox::InboundEmail.table_name}}) %>
+ <br />To resolve this issue run: rails action_mailbox:install
+ <% end %>
</h2>
<%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb
index e5e3196710..16c3ecc331 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb
@@ -4,8 +4,10 @@
<% end %>
<%= @exception.message %>
-<% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
+<% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
To resolve this issue run: rails active_storage:install
+<% if defined?(ActionMailbox) && @exception.message.match?(%r{#{ActionMailbox::InboundEmail.table_name}}) %>
+To resolve this issue run: rails action_mailbox:install
<% end %>
<%= render template: "rescues/_source" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index 39ea25bdfc..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,7 +118,13 @@
}
.line.active {
- background-color: #FFCCCC;
+ background-color: #FCC;
+ }
+
+ .button_to {
+ display: inline-block;
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
}
.hidden {
@@ -123,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.rb b/actionpack/lib/action_dispatch/routing.rb
index 5cde677051..d78b1c4f71 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -74,8 +74,8 @@ module ActionDispatch
# For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper
# methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
#
- # get 'post/:id' => 'posts#show'
- # post 'post/:id' => 'posts#create_comment'
+ # get 'post/:id', to: 'posts#show'
+ # post 'post/:id', to: 'posts#create_comment'
#
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
# URL will route to the <tt>show</tt> action.
@@ -83,7 +83,7 @@ module ActionDispatch
# If your route needs to respond to more than one HTTP method (or all methods) then using the
# <tt>:via</tt> option on <tt>match</tt> is preferable.
#
- # match 'post/:id' => 'posts#show', via: [:get, :post]
+ # match 'post/:id', to: 'posts#show', via: [:get, :post]
#
# == Named routes
#
@@ -94,7 +94,7 @@ module ActionDispatch
# Example:
#
# # In config/routes.rb
- # get '/login' => 'accounts#login', as: 'login'
+ # get '/login', to: 'accounts#login', as: 'login'
#
# # With render, redirect_to, tests, etc.
# redirect_to login_url
@@ -120,9 +120,9 @@ module ActionDispatch
#
# # In config/routes.rb
# controller :blog do
- # get 'blog/show' => :list
- # get 'blog/delete' => :delete
- # get 'blog/edit' => :edit
+ # get 'blog/show', to: :list
+ # get 'blog/delete', to: :delete
+ # get 'blog/edit', to: :edit
# end
#
# # provides named routes for show, delete, and edit
@@ -132,7 +132,7 @@ module ActionDispatch
#
# Routes can generate pretty URLs. For example:
#
- # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
+ # get '/articles/:year/:month/:day', to: 'articles#find_by_id', constraints: {
# year: /\d{4}/,
# month: /\d{1,2}/,
# day: /\d{1,2}/
@@ -147,7 +147,7 @@ module ActionDispatch
# You can specify a regular expression to define a format for a parameter.
#
# controller 'geocode' do
- # get 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode', to: :show, constraints: {
# postalcode: /\d{5}(-\d{4})?/
# }
# end
@@ -156,13 +156,13 @@ module ActionDispatch
# expression modifiers:
#
# controller 'geocode' do
- # get 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode', to: :show, constraints: {
# postalcode: /hx\d\d\s\d[a-z]{2}/i
# }
# end
#
# controller 'geocode' do
- # get 'geocode/:postalcode' => :show, constraints: {
+ # get 'geocode/:postalcode', to: :show, constraints: {
# postalcode: /# Postalcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
@@ -178,13 +178,13 @@ module ActionDispatch
#
# You can redirect any path to another path using the redirect helper in your router:
#
- # get "/stories" => redirect("/posts")
+ # get "/stories", to: redirect("/posts")
#
# == Unicode character routes
#
# You can specify unicode character routes in your router:
#
- # get "こんにちは" => "welcome#index"
+ # get "こんにちは", to: "welcome#index"
#
# == Routing to Rack Applications
#
@@ -192,7 +192,7 @@ module ActionDispatch
# index action in the PostsController, you can specify any Rack application
# as the endpoint for a matcher:
#
- # get "/application.js" => Sprockets
+ # get "/application.js", to: Sprockets
#
# == Reloading routes
#
@@ -210,8 +210,8 @@ module ActionDispatch
# === +assert_routing+
#
# def test_movie_route_properly_splits
- # opts = {controller: "plugin", action: "checkout", id: "2"}
- # assert_routing "plugin/checkout/2", opts
+ # opts = {controller: "plugin", action: "checkout", id: "2"}
+ # assert_routing "plugin/checkout/2", opts
# end
#
# +assert_routing+ lets you test whether or not the route properly resolves into options.
@@ -219,8 +219,8 @@ module ActionDispatch
# === +assert_recognizes+
#
# def test_route_has_options
- # opts = {controller: "plugin", action: "show", id: "12"}
- # assert_recognizes opts, "/plugins/show/12"
+ # opts = {controller: "plugin", action: "show", id: "12"}
+ # assert_recognizes opts, "/plugins/show/12"
# end
#
# Note the subtle difference between the two: +assert_routing+ tests that
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 06ce165f76..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,33 +115,33 @@ 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
-
- @to = to
- @default_controller = controller
- @default_action = default_action
+ 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)
@ast = ast
@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,17 +164,10 @@ module ActionDispatch
end
def make_route(name, precedence)
- route = Journey::Route.new(name,
- application,
- path,
- conditions,
- required_defaults,
- defaults,
- request_method,
- precedence,
- @internal)
-
- route
+ 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
@@ -231,6 +228,10 @@ module ActionDispatch
private :build_path
private
+ def intern(object)
+ object.is_a?(String) ? -object : object
+ end
+
def add_wildcard_options(options, formatted, path_ast)
# Add a constraint for wildcard route to make it non-greedy and match the
# optional format part of the route by default.
@@ -656,7 +657,7 @@ module ActionDispatch
# Query if the following named route was already defined.
def has_named_route?(name)
- @set.named_routes.key? name
+ @set.named_routes.key?(name)
end
private
@@ -1150,6 +1151,10 @@ module ActionDispatch
attr_reader :controller, :path, :param
def initialize(entities, api_only, shallow, options = {})
+ if options[:param].to_s.include?(":")
+ raise ArgumentError, ":param option can't contain colons"
+ end
+
@name = entities.to_s
@path = (options[:path] || @name).to_s
@controller = (options[:controller] || @name).to_s
@@ -1407,6 +1412,8 @@ module ActionDispatch
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
# to be shortened to just <tt>/comments/1234</tt>.
#
+ # Set <tt>shallow: false</tt> on a child resource to ignore a parent's shallow parameter.
+ #
# [:shallow_path]
# Prefixes nested shallow routes with the specified path.
#
@@ -1449,6 +1456,9 @@ module ActionDispatch
# Allows you to specify the default value for optional +format+
# segment or disable it by supplying +false+.
#
+ # [:param]
+ # Allows you to override the default param name of +:id+ in the URL.
+ #
# === Examples
#
# # routes call <tt>Admin::PostsController</tt>
@@ -1663,7 +1673,6 @@ module ActionDispatch
end
private
-
def parent_resource
@scope[:scope_level_resource]
end
@@ -1674,7 +1683,8 @@ module ActionDispatch
return true
end
- if options.delete(:shallow)
+ if options[:shallow]
+ options.delete(:shallow)
shallow do
send(method, resources.pop, options, &block)
end
@@ -1952,9 +1962,7 @@ module ActionDispatch
end
def match_root_route(options)
- name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
- args = ["/", { as: name, via: :get }.merge!(options)]
-
+ args = ["/", { as: :root, via: :get }.merge(options)]
match(*args)
end
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 2ae75b0da8..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
@@ -90,11 +88,11 @@ module ActionDispatch
def clear!
@path_helpers.each do |helper|
- @path_helpers_module.send :remove_method, helper
+ @path_helpers_module.remove_method helper
end
@url_helpers.each do |helper|
- @url_helpers_module.send :remove_method, helper
+ @url_helpers_module.remove_method helper
end
@routes.clear
@@ -108,8 +106,8 @@ module ActionDispatch
url_name = :"#{name}_url"
if routes.key? key
- @path_helpers_module.send :undef_method, path_name
- @url_helpers_module.send :undef_method, url_name
+ @path_helpers_module.undef_method path_name
+ @url_helpers_module.undef_method url_name
end
routes[key] = route
define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
@@ -215,7 +213,6 @@ module ActionDispatch
end
private
-
def optimized_helper(args)
params = parameterize_args(args) do
raise_generation_error(args)
@@ -317,23 +314,21 @@ module ActionDispatch
#
def define_url_helper(mod, route, name, opts, route_key, url_strategy)
helper = UrlHelper.create(route, opts, route_key, url_strategy)
- mod.module_eval do
- define_method(name) do |*args|
- last = args.last
- options = \
- case last
- when Hash
- args.pop
- when ActionController::Parameters
- args.pop.to_h
- end
- helper.call self, args, options
- end
+ mod.define_method(name) do |*args|
+ last = args.last
+ options = \
+ case last
+ when Hash
+ args.pop
+ when ActionController::Parameters
+ args.pop.to_h
+ end
+ helper.call self, args, options
end
end
end
- # strategy for building urls to send to the client
+ # strategy for building URLs to send to the client
PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
@@ -593,14 +588,14 @@ module ActionDispatch
if route.segment_keys.include?(:controller)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :controller segment in a route is deprecated and
- will be removed in Rails 6.0.
+ will be removed in Rails 6.1.
MSG
end
if route.segment_keys.include?(:action)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :action segment in a route is deprecated and
- will be removed in Rails 6.0.
+ will be removed in Rails 6.1.
MSG
end
@@ -820,6 +815,10 @@ module ActionDispatch
path, params = generate(route_name, path_options, recall)
+ if options.key? :params
+ params.merge! options[:params]
+ end
+
options[:path] = path
options[:script_name] = script_name
options[:params] = params
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 1a31c7dbb8..e02a6541c1 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -133,6 +133,7 @@ module ActionDispatch
# <tt>ActionDispatch::Http::URL.tld_length</tt>, which in turn defaults to 1.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
+ # * <tt>:params</tt> - The query parameters to be appended to the path.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
# * <tt>:script_name</tt> - Specifies application path relative to domain root. If provided, prepends application path.
#
@@ -214,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 c74c0ccced..4fda2cf44f 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -4,13 +4,13 @@ gem "capybara", ">= 2.15"
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
@@ -89,19 +89,47 @@ module ActionDispatch
# { js_errors: true }
# end
#
+ # Some drivers require browser capabilities to be passed as a block instead
+ # of through the +options+ hash.
+ #
+ # As an example, if you want to add mobile emulation on chrome, you'll have to
+ # create an instance of selenium's +Chrome::Options+ object and add
+ # capabilities with a block.
+ #
+ # The block will be passed an instance of <tt><Driver>::Options</tt> where you can
+ # define the capabilities you want. Please refer to your driver documentation
+ # to learn about supported options.
+ #
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ # driven_by :selenium, using: :chrome, screen_size: [1024, 768] do |driver_option|
+ # driver_option.add_emulation(device_name: 'iPhone 6')
+ # driver_option.add_extension('path/to/chrome_extension.crx')
+ # end
+ # end
+ #
# 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
self.class.driver.use
+ @proxy_route = if ActionDispatch.test_app
+ Class.new do
+ include ActionDispatch.test_app.routes.url_helpers
+
+ def url_options
+ default_url_options.merge(host: Capybara.app_host)
+ end
+ end.new
+ else
+ nil
+ end
end
def self.start_application # :nodoc:
@@ -134,12 +162,22 @@ module ActionDispatch
# driven_by :selenium, using: :firefox
#
# driven_by :selenium, using: :headless_firefox
- def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {})
- self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options)
+ def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}, &capabilities)
+ driver_options = { using: using, screen_size: screen_size, options: options }
+
+ self.driver = SystemTesting::Driver.new(driver, driver_options, &capabilities)
end
driven_by :selenium
+ def method_missing(method, *args, &block)
+ if @proxy_route.respond_to?(method)
+ @proxy_route.send(method, *args, &block)
+ else
+ super
+ end
+ end
+
ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self)
end
diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb
index 1b0bce6b9e..e861e52f09 100644
--- a/actionpack/lib/action_dispatch/system_testing/browser.rb
+++ b/actionpack/lib/action_dispatch/system_testing/browser.rb
@@ -29,20 +29,51 @@ module ActionDispatch
end
end
+ def capabilities
+ @option ||=
+ case type
+ when :chrome
+ ::Selenium::WebDriver::Chrome::Options.new
+ when :firefox
+ ::Selenium::WebDriver::Firefox::Options.new
+ 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
- options = Selenium::WebDriver::Chrome::Options.new
- options.args << "--headless"
- options.args << "--disable-gpu" if Gem.win_platform?
+ capabilities.args << "--headless"
+ capabilities.args << "--disable-gpu" if Gem.win_platform?
- options
+ capabilities
end
def headless_firefox_browser_options
- options = Selenium::WebDriver::Firefox::Options.new
- options.args << "-headless"
+ capabilities.args << "-headless"
- options
+ capabilities
end
end
end
diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb
index 5252ff6746..15943a55ea 100644
--- a/actionpack/lib/action_dispatch/system_testing/driver.rb
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -3,11 +3,14 @@
module ActionDispatch
module SystemTesting
class Driver # :nodoc:
- def initialize(name, **options)
+ def initialize(name, **options, &capabilities)
@name = name
@browser = Browser.new(options[:using])
@screen_size = options[:screen_size]
@options = options[:options]
+ @capabilities = capabilities
+
+ @browser.preload
end
def use
@@ -22,6 +25,8 @@ module ActionDispatch
end
def register
+ define_browser_capabilities(@browser.capabilities)
+
Capybara.register_driver @name do |app|
case @name
when :selenium then register_selenium(app)
@@ -31,6 +36,10 @@ module ActionDispatch
end
end
+ def define_browser_capabilities(capabilities)
+ @capabilities.call(capabilities) if @capabilities
+ end
+
def browser_options
@options.merge(options: @browser.options).compact
end
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
index 884fb51d18..056ce51a61 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
@@ -20,7 +20,7 @@ module ActionDispatch
# * [+inline+] Display the screenshot in the terminal using the
# iTerm image protocol (https://iterm2.com/documentation-images.html).
# * [+artifact+] Display the screenshot in the terminal, using the terminal
- # artifact format (https://buildkite.github.io/terminal/inline-images/).
+ # artifact format (https://buildkite.github.io/terminal-to-html/inline-images/).
def take_screenshot
save_image
puts display_image
@@ -39,7 +39,8 @@ module ActionDispatch
private
def image_name
- failed? ? "failures_#{method_name}" : method_name
+ name = method_name[0...225]
+ failed? ? "failures_#{name}" : name
end
def image_path
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 600e9c733b..20f6a7634f 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
@@ -7,7 +7,6 @@ module ActionDispatch
DEFAULT_HOST = "http://127.0.0.1"
def host!(host)
- super
Capybara.app_host = host
end
@@ -16,12 +15,14 @@ module ActionDispatch
super
end
+ def before_teardown
+ take_failed_screenshot
+ ensure
+ super
+ end
+
def after_teardown
- begin
- take_failed_screenshot
- ensure
- Capybara.reset_sessions!
- end
+ Capybara.reset_sessions!
ensure
super
end
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/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index af41521c5c..28cde6704e 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -160,9 +160,16 @@ module ActionDispatch
@controller.singleton_class.include(_routes.url_helpers)
if @controller.respond_to? :view_context_class
- @controller.view_context_class = Class.new(@controller.view_context_class) do
+ view_context_class = Class.new(@controller.view_context_class) do
include _routes.url_helpers
end
+
+ custom_view_context = Module.new {
+ define_method(:view_context_class) do
+ view_context_class
+ end
+ }
+ @controller.extend(custom_view_context)
end
end
yield @routes
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 45439a3bb1..c5f8b816a4 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"
@@ -194,7 +193,7 @@ module ActionDispatch
# Adds request headers characteristic of XMLHttpRequest e.g. HTTP_X_REQUESTED_WITH.
# The headers will be merged into the Rack env hash.
# - +as+: Used for encoding the request with different content type.
- # Supports `:json` by default and will set the approriate request headers.
+ # Supports `:json` by default and will set the appropriate request headers.
# The headers will be merged into the Rack env hash.
#
# This method is rarely used directly. Use +#get+, +#post+, or other standard
@@ -335,7 +334,7 @@ module ActionDispatch
klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) {
# If the app is a Rails app, make url_helpers available on the session.
# This makes app.url_for and app.foo_path available in the console.
- if app.respond_to?(:routes)
+ if app.respond_to?(:routes) && app.routes.is_a?(ActionDispatch::Routing::RouteSet)
include app.routes.url_helpers
include app.routes.mounted_helpers
end
diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb
index 9889f61951..6c65bec62f 100644
--- a/actionpack/lib/action_dispatch/testing/request_encoder.rb
+++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb
@@ -38,8 +38,8 @@ module ActionDispatch
end
def self.parser(content_type)
- mime = Mime::Type.lookup(content_type)
- encoder(mime ? mime.ref : nil).response_parser
+ type = Mime::Type.lookup(content_type).ref if content_type
+ encoder(type).response_parser
end
def self.encoder(name)
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 1e6b21f235..f1dd4099c5 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -14,40 +14,12 @@ module ActionDispatch
new response.status, response.headers, response.body
end
- def initialize(*) # :nodoc:
- super
- @response_parser = RequestEncoder.parser(content_type)
- end
-
- # Was the response successful?
- def success?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The success? predicate is deprecated and will be removed in Rails 6.0.
- Please use successful? as provided by Rack::Response::Helpers.
- MSG
- successful?
- end
-
- # Was the URL not found?
- def missing?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The missing? predicate is deprecated and will be removed in Rails 6.0.
- Please use not_found? as provided by Rack::Response::Helpers.
- MSG
- not_found?
- end
-
- # Was there a server-side error?
- def error?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The error? predicate is deprecated and will be removed in Rails 6.0.
- Please use server_error? as provided by Rack::Response::Helpers.
- MSG
- server_error?
+ def parsed_body
+ @parsed_body ||= response_parser.call(body)
end
- def parsed_body
- @parsed_body ||= @response_parser.call(body)
+ def response_parser
+ @response_parser ||= RequestEncoder.parser(media_type)
end
end
end
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 3f69109633..36ee77c693 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2018 David Heinemeier Hansson
+# Copyright (c) 2004-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 37969fcb57..5f8905139d 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -8,7 +8,7 @@ module ActionPack
module VERSION
MAJOR = 6
- MINOR = 0
+ MINOR = 1
TINY = 0
PRE = "alpha"
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index a4770b66e1..6db045fcd7 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -30,7 +30,7 @@ module AbstractController
end
test "register mime types on method missing" do
- AbstractController::Collector.send(:remove_method, :js)
+ AbstractController::Collector.remove_method :js
begin
collector = MyCollector.new
assert_not_respond_to collector, :js
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 65dd28b3d7..1decfcee95 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -13,13 +13,6 @@ silence_warnings do
Encoding.default_external = Encoding::UTF_8
end
-require "drb"
-begin
- require "drb/unix"
-rescue LoadError
- puts "'drb/unix' is not available"
-end
-
if ENV["TRAVIS"]
PROCESS_COUNT = 0
else
@@ -80,7 +73,7 @@ end
module ActiveSupport
class TestCase
if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0
- parallelize_me!
+ parallelize(workers: PROCESS_COUNT)
end
end
end
@@ -103,6 +96,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
middleware.use ActionDispatch::DebugExceptions
+ middleware.use ActionDispatch::ActionableExceptions
middleware.use ActionDispatch::Callbacks
middleware.use ActionDispatch::Cookies
middleware.use ActionDispatch::Flash
@@ -341,7 +335,6 @@ module RoutingTestHelpers
end
private
-
def make_request(env)
Request.new super, url_helpers, @block, strict
end
@@ -359,75 +352,6 @@ class ImagesController < ResourcesController; end
require "active_support/testing/method_call_assertions"
-class ForkingExecutor
- class Server
- include DRb::DRbUndumped
-
- def initialize
- @queue = Queue.new
- end
-
- def record(reporter, result)
- reporter.record result
- end
-
- def <<(o)
- o[2] = DRbObject.new(o[2]) if o
- @queue << o
- end
- def pop; @queue.pop; end
- end
-
- def initialize(size)
- @size = size
- @queue = Server.new
- @pool = nil
- @url = DRb.start_service("drbunix:", @queue).uri
- end
-
- def <<(work); @queue << work; end
-
- def shutdown
- pool = @size.times.map {
- fork {
- DRb.stop_service
- queue = DRbObject.new_with_uri @url
- while job = queue.pop
- klass = job[0]
- method = job[1]
- reporter = job[2]
- result = Minitest.run_one_method klass, method
- if result.error?
- translate_exceptions result
- end
- queue.record reporter, result
- end
- }
- }
- @size.times { @queue << nil }
- pool.each { |pid| Process.waitpid pid }
- end
-
- private
- def translate_exceptions(result)
- result.failures.map! { |e|
- begin
- Marshal.dump e
- e
- rescue TypeError
- ex = Exception.new e.message
- ex.set_backtrace e.backtrace
- Minitest::UnexpectedError.new ex
- end
- }
- end
-end
-
-if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0
- # Use N processes (N defaults to 4)
- Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT)
-end
-
class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
@@ -458,3 +382,5 @@ end
class DrivenBySeleniumWithHeadlessFirefox < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_firefox
end
+
+require_relative "../../tools/test_common"
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 763df3a776..51286155b9 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -276,16 +276,14 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
end
def test_assert_redirect_failure_message_with_protocol_relative_url
- begin
- process :redirect_external_protocol_relative
- assert_redirected_to "/foo"
- rescue ActiveSupport::TestCase::Assertion => ex
- assert_no_match(
- /#{request.protocol}#{request.host}\/\/www.rubyonrails.org/,
- ex.message,
- "protocol relative url was incorrectly normalized"
- )
- end
+ process :redirect_external_protocol_relative
+ assert_redirected_to "/foo"
+ rescue ActiveSupport::TestCase::Assertion => ex
+ assert_no_match(
+ /#{request.protocol}#{request.host}\/\/www.rubyonrails.org/,
+ ex.message,
+ "protocol relative URL was incorrectly normalized"
+ )
end
def test_template_objects_exist
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/base_test.rb b/actionpack/test/controller/base_test.rb
index 558e710df9..d8cea10153 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -193,7 +193,7 @@ class UrlOptionsTest < ActionController::TestCase
action: "home",
controller: "pages",
only_path: true,
- token: "secret"
+ params: { "token" => "secret" }
}
assert_equal "/home?token=secret", rs.url_for(options)
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 6fe036dd15..f09e812147 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -60,14 +60,6 @@ class FragmentCachingTest < ActionController::TestCase
@m2v2 = ModelWithKeyAndVersion.new("model/2", "2")
end
- def test_fragment_cache_key
- assert_deprecated do
- assert_equal "views/what a key", @controller.fragment_cache_key("what a key")
- assert_equal "views/test.host/fragment_caching_test/some_action",
- @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
- end
- end
-
def test_combined_fragment_cache_key
assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key")
assert_equal [ :views, "test.host/fragment_caching_test/some_action" ],
@@ -220,7 +212,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "This bit's fragment cached",
- @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached")}/fragment")
+ @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached", "html")}/fragment")
end
def test_fragment_caching_in_partials
@@ -229,7 +221,7 @@ CACHED
assert_match(/Old fragment caching in a partial/, @response.body)
assert_match("Old fragment caching in a partial",
- @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial")}/test.host/functional_caching/html_fragment_cached_with_partial"))
+ @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial", "html")}/test.host/functional_caching/html_fragment_cached_with_partial"))
end
def test_skipping_fragment_cache_digesting
@@ -259,7 +251,7 @@ CACHED
assert_match(/Some inline content/, @response.body)
assert_match(/Some cached content/, @response.body)
assert_match("Some cached content",
- @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached")}/test.host/functional_caching/inline_fragment_cached"))
+ @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached", "html")}/test.host/functional_caching/inline_fragment_cached"))
end
def test_fragment_cache_instrumentation
@@ -279,36 +271,39 @@ CACHED
end
def test_html_formatted_fragment_caching
- get :formatted_fragment_cached, format: "html"
+ format = "html"
+ get :formatted_fragment_cached, format: format
assert_response :success
expected_body = "<body>\n<p>ERB</p>\n</body>\n"
assert_equal expected_body, @response.body
assert_equal "<p>ERB</p>",
- @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment")
+ @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached", format)}/fragment")
end
def test_xml_formatted_fragment_caching
- get :formatted_fragment_cached, format: "xml"
+ format = "xml"
+ get :formatted_fragment_cached, format: format
assert_response :success
expected_body = "<body>\n <p>Builder</p>\n</body>\n"
assert_equal expected_body, @response.body
assert_equal " <p>Builder</p>\n",
- @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment")
+ @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached", format)}/fragment")
end
def test_fragment_caching_with_variant
- get :formatted_fragment_cached_with_variant, format: "html", params: { v: :phone }
+ format = "html"
+ get :formatted_fragment_cached_with_variant, format: format, params: { v: :phone }
assert_response :success
expected_body = "<body>\n<p>PHONE</p>\n</body>\n"
assert_equal expected_body, @response.body
assert_equal "<p>PHONE</p>",
- @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}/fragment")
+ @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant", format)}/fragment")
end
def test_fragment_caching_with_html_partials_in_xml
@@ -317,8 +312,8 @@ CACHED
end
private
- def template_digest(name)
- ActionView::Digestor.digest(name: name, finder: @controller.lookup_context)
+ def template_digest(name, format)
+ ActionView::Digestor.digest(name: name, format: format, finder: @controller.lookup_context)
end
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 425a6e25cc..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"
@@ -457,6 +456,7 @@ class FilterTest < ActionController::TestCase
prepend_before_action :before_all
prepend_after_action :after_all
before_action :between_before_all_and_after_all
+ after_action :between_before_all_and_after_all
def before_all
@ran_filter ||= []
@@ -472,6 +472,7 @@ class FilterTest < ActionController::TestCase
@ran_filter ||= []
@ran_filter << "between_before_all_and_after_all"
end
+
def show
render plain: "hello"
end
@@ -506,7 +507,6 @@ class FilterTest < ActionController::TestCase
end
private
-
def filter_one
@filters ||= []
@filters << "filter_one"
@@ -530,7 +530,6 @@ class FilterTest < ActionController::TestCase
before_action :find_except, except: :edit
private
-
def find_only
@only = "Only"
end
@@ -765,7 +764,7 @@ class FilterTest < ActionController::TestCase
def test_running_prepended_before_and_after_action
test_process(PrependingBeforeAndAfterController)
- assert_equal %w( before_all between_before_all_and_after_all after_all ), @controller.instance_variable_get(:@ran_filter)
+ assert_equal %w( before_all between_before_all_and_after_all between_before_all_and_after_all after_all ), @controller.instance_variable_get(:@ran_filter)
end
def test_skipping_and_limiting_controller
@@ -886,7 +885,7 @@ class ControllerWithSymbolAsFilter < PostsController
yield
# Do stuff...
- wtf += 1
+ wtf + 1
end
end
@@ -998,16 +997,12 @@ class YieldingAroundFiltersTest < ActionController::TestCase
def test_nested_actions
controller = ControllerWithNestedFilters
assert_nothing_raised do
- begin
- test_process(controller, "raises_both")
- rescue Before, After
- end
+ test_process(controller, "raises_both")
+ rescue Before, After
end
assert_raise Before do
- begin
- test_process(controller, "raises_both")
- rescue After
- end
+ test_process(controller, "raises_both")
+ rescue After
end
end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 409a4ec2e6..1f44c7a68e 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -242,8 +242,11 @@ end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = "_myapp_session"
- Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
- Rotations = ActiveSupport::Messages::RotationConfiguration.new
+ Generator = ActiveSupport::CachingKeyGenerator.new(
+ ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000)
+ )
+ Rotations = ActiveSupport::Messages::RotationConfiguration.new
+ SIGNED_COOKIE_SALT = "signed cookie"
class TestController < ActionController::Base
add_flash_types :bar
@@ -358,13 +361,13 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
end
private
-
# Overwrite get to send SessionSecret in env hash
def get(path, *args)
args[0] ||= {}
args[0][:env] ||= {}
args[0][:env]["action_dispatch.key_generator"] ||= Generator
args[0][:env]["action_dispatch.cookies_rotations"] = Rotations
+ args[0][:env]["action_dispatch.signed_cookie_salt"] = SIGNED_COOKIE_SALT
super(path, *args)
end
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 b133afb343..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
@@ -44,7 +43,10 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
setup do
# Used as secret in generating nonce to prevent tampering of timestamp
@secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(@secret)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::CachingKeyGenerator.new(
+ ActiveSupport::KeyGenerator.new(@secret)
+ )
+ @request.env["action_dispatch.http_auth_salt"] = "http authentication"
end
teardown do
@@ -251,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 39ede1442a..cce229b30d 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -152,7 +152,7 @@ class IntegrationTestTest < ActiveSupport::TestCase
assert_equal "pass", @test.foo
ensure
# leave other tests as unaffected as possible
- mixin.__send__(:remove_method, :method_missing)
+ mixin.remove_method :method_missing
end
end
end
@@ -522,11 +522,11 @@ 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
@@ -808,17 +808,17 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
end
end
- test "session uses default url options from routes" do
+ test "session uses default URL options from routes" do
assert_equal "http://foo.com/foo", foos_url
end
- test "current host overrides default url options from routes" do
+ test "current host overrides default URL options from routes" do
get "/foo"
assert_response :success
assert_equal "http://www.example.com/foo", foos_url
end
- test "controller can override default url options from request" do
+ test "controller can override default URL options from request" do
get "/bar"
assert_response :success
assert_equal "http://bar.com/foo", foos_url
@@ -986,7 +986,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 +1025,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/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 0562c16284..1a7e7f6cbb 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -98,6 +98,7 @@ class ACLogSubscriberTest < ActionController::TestCase
@cache_path = Dir.mktmpdir(%w[tmp cache])
@controller.cache_store = :file_store, @cache_path
+ @controller.config.perform_caching = true
ActionController::LogSubscriber.attach_to :action_controller
end
@@ -249,19 +250,15 @@ class ACLogSubscriberTest < ActionController::TestCase
end
def test_with_fragment_cache
- @controller.config.perform_caching = true
get :with_fragment_cache
wait
assert_equal 4, logs.size
assert_match(/Read fragment views\/foo/, logs[1])
assert_match(/Write fragment views\/foo/, logs[2])
- ensure
- @controller.config.perform_caching = true
end
def test_with_fragment_cache_when_log_disabled
- @controller.config.perform_caching = true
ActionController::Base.enable_fragment_cache_logging = false
get :with_fragment_cache
wait
@@ -269,69 +266,52 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_equal 2, logs.size
assert_equal "Processing by Another::LogSubscribersController#with_fragment_cache as HTML", logs[0]
assert_match(/Completed 200 OK in \d+ms/, logs[1])
- ensure
- @controller.config.perform_caching = true
ActionController::Base.enable_fragment_cache_logging = true
end
def test_with_fragment_cache_if_with_true
- @controller.config.perform_caching = true
get :with_fragment_cache_if_with_true_condition
wait
assert_equal 4, logs.size
assert_match(/Read fragment views\/foo/, logs[1])
assert_match(/Write fragment views\/foo/, logs[2])
- ensure
- @controller.config.perform_caching = true
end
def test_with_fragment_cache_if_with_false
- @controller.config.perform_caching = true
get :with_fragment_cache_if_with_false_condition
wait
assert_equal 2, logs.size
assert_no_match(/Read fragment views\/foo/, logs[1])
assert_no_match(/Write fragment views\/foo/, logs[2])
- ensure
- @controller.config.perform_caching = true
end
def test_with_fragment_cache_unless_with_true
- @controller.config.perform_caching = true
get :with_fragment_cache_unless_with_true_condition
wait
assert_equal 2, logs.size
assert_no_match(/Read fragment views\/foo/, logs[1])
assert_no_match(/Write fragment views\/foo/, logs[2])
- ensure
- @controller.config.perform_caching = true
end
def test_with_fragment_cache_unless_with_false
- @controller.config.perform_caching = true
get :with_fragment_cache_unless_with_false_condition
wait
assert_equal 4, logs.size
assert_match(/Read fragment views\/foo/, logs[1])
assert_match(/Write fragment views\/foo/, logs[2])
- ensure
- @controller.config.perform_caching = true
end
def test_with_fragment_cache_and_percent_in_key
- @controller.config.perform_caching = true
get :with_fragment_cache_and_percent_in_key
wait
assert_equal 4, logs.size
assert_match(/Read fragment views\/foo/, logs[1])
assert_match(/Write fragment views\/foo/, logs[2])
- ensure
- @controller.config.perform_caching = true
end
def test_process_action_with_exception_includes_http_status_code
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 00e1d5f3b3..fc16c639fb 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -125,7 +125,7 @@ class RespondToController < ActionController::Base
def custom_type_handling
respond_to do |type|
type.html { render body: "HTML" }
- type.custom("application/crazy-xml") { render body: "Crazy XML" }
+ type.custom("application/fancy-xml") { render body: "Fancy XML" }
type.all { render body: "Nothing" }
end
end
@@ -158,6 +158,12 @@ class RespondToController < ActionController::Base
end
end
+ def handle_any_with_template
+ respond_to do |type|
+ type.any { render "test/hello_world" }
+ end
+ end
+
def all_types_with_layout
respond_to do |type|
type.html
@@ -314,12 +320,14 @@ class RespondToControllerTest < ActionController::TestCase
@request.host = "www.example.com"
Mime::Type.register_alias("text/html", :iphone)
Mime::Type.register("text/x-mobile", :mobile)
+ Mime::Type.register("application/fancy-xml", :fancy_xml)
end
def teardown
super
Mime::Type.unregister(:iphone)
Mime::Type.unregister(:mobile)
+ Mime::Type.unregister(:fancy_xml)
end
def test_html
@@ -415,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
@@ -441,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
@@ -460,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
@@ -489,14 +497,14 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_custom_types
- @request.accept = "application/crazy-xml"
+ @request.accept = "application/fancy-xml"
get :custom_type_handling
- assert_equal "application/crazy-xml", @response.content_type
- assert_equal "Crazy XML", @response.body
+ 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
@@ -570,6 +578,13 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "HTML", @response.body
end
+ def test_handle_any_with_template
+ @request.accept = "*/*"
+
+ get :handle_any_with_template
+ assert_equal "Hello world!", @response.body
+ end
+
def test_html_type_with_layout
@request.accept = "text/html"
get :all_types_with_layout
@@ -580,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
@@ -590,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
@@ -649,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
@@ -657,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
@@ -687,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
@@ -741,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/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb
index 7205e90176..548fa4300d 100644
--- a/actionpack/test/controller/new_base/content_negotiation_test.rb
+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb
@@ -20,9 +20,19 @@ module ContentNegotiation
assert_body "Hello world */*!"
end
- test "Not all mimes are converted to symbol" do
+ test "A js or */* Accept header will return HTML" do
+ get "/content_negotiation/basic/hello", headers: { "HTTP_ACCEPT" => "text/javascript, */*" }
+ assert_body "Hello world text/html!"
+ end
+
+ test "A js or */* Accept header on xhr will return JavaScript" do
+ get "/content_negotiation/basic/hello", headers: { "HTTP_ACCEPT" => "text/javascript, */*" }, xhr: true
+ assert_body "Hello world text/javascript!"
+ end
+
+ test "Unregistered mimes are ignored" do
get "/content_negotiation/basic/all", headers: { "HTTP_ACCEPT" => "text/plain, mime/another" }
- assert_body '[:text, "mime/another"]'
+ assert_body "[:text]"
end
end
end
diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb
deleted file mode 100644
index 5e570a1d79..0000000000
--- a/actionpack/test/controller/new_base/render_context_test.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require "abstract_unit"
-
-# This is testing the decoupling of view renderer and view context
-# by allowing the controller to be used as view context. This is
-# similar to the way sinatra renders templates.
-module RenderContext
- class BasicController < ActionController::Base
- self.view_paths = [ActionView::FixtureResolver.new(
- "render_context/basic/hello_world.html.erb" => "<%= @value %> from <%= self.__controller_method__ %>",
- "layouts/basic.html.erb" => "?<%= yield %>?"
- )]
-
- # 1) Include ActionView::Context to bring the required dependencies
- include ActionView::Context
-
- # 2) Call _prepare_context that will do the required initialization
- before_action :_prepare_context
-
- def hello_world
- @value = "Hello"
- render action: "hello_world", layout: false
- end
-
- def with_layout
- @value = "Hello"
- render action: "hello_world", layout: "basic"
- end
-
- protected def __controller_method__
- "controller context!"
- end
-
- private
- # 3) Set view_context to self
- def view_context
- self
- end
- end
-
- class RenderContextTest < Rack::TestCase
- test "rendering using the controller as context" do
- get "/render_context/basic/hello_world"
- assert_body "Hello from controller context!"
- assert_status 200
- end
-
- test "rendering using the controller as context with layout" do
- get "/render_context/basic/with_layout"
- assert_body "?Hello from controller context!?"
- assert_status 200
- end
- end
-end
diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb
index de8af029e0..01d0223519 100644
--- a/actionpack/test/controller/new_base/render_file_test.rb
+++ b/actionpack/test/controller/new_base/render_file_test.rb
@@ -17,12 +17,12 @@ module RenderFile
def relative_path
@secret = "in the sauce"
- render file: "../../fixtures/test/render_file_with_ivar"
+ render file: "../actionpack/test/fixtures/test/render_file_with_ivar"
end
def relative_path_with_dot
@secret = "in the sauce"
- render file: "../../fixtures/test/dot.directory/render_file_with_ivar"
+ render file: "../actionpack/test/fixtures/test/dot.directory/render_file_with_ivar"
end
def pathname
@@ -40,32 +40,44 @@ module RenderFile
testing RenderFile::BasicController
test "rendering simple template" do
- get :index
+ assert_deprecated do
+ get :index
+ end
assert_response "Hello world!"
end
test "rendering template with ivar" do
- get :with_instance_variables
+ assert_deprecated do
+ get :with_instance_variables
+ end
assert_response "The secret is in the sauce\n"
end
test "rendering a relative path" do
- get :relative_path
+ assert_deprecated do
+ get :relative_path
+ end
assert_response "The secret is in the sauce\n"
end
test "rendering a relative path with dot" do
- get :relative_path_with_dot
+ assert_deprecated do
+ get :relative_path_with_dot
+ end
assert_response "The secret is in the sauce\n"
end
test "rendering a Pathname" do
- get :pathname
+ assert_deprecated do
+ get :pathname
+ end
assert_response "The secret is in the sauce\n"
end
test "rendering file with locals" do
- get :with_locals
+ assert_deprecated do
+ get :with_locals
+ end
assert_response "The secret is in the sauce\n"
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..31ee7964f5 100644
--- a/actionpack/test/controller/parameters/mutators_test.rb
+++ b/actionpack/test/controller/parameters/mutators_test.rb
@@ -118,4 +118,13 @@ 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
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/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index d2fa0aa16e..fbfe24059b 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -365,17 +365,15 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "permitted takes a default value when Parameters.permit_all_parameters is set" do
- begin
- ActionController::Parameters.permit_all_parameters = true
- params = ActionController::Parameters.new(person: {
- age: "32", name: { first: "David", last: "Heinemeier Hansson" }
- })
-
- assert_predicate params.slice(:person), :permitted?
- assert_predicate params[:person][:name], :permitted?
- ensure
- ActionController::Parameters.permit_all_parameters = false
- end
+ ActionController::Parameters.permit_all_parameters = true
+ params = ActionController::Parameters.new(person: {
+ age: "32", name: { first: "David", last: "Heinemeier Hansson" }
+ })
+
+ assert_predicate params.slice(:person), :permitted?
+ assert_predicate params[:person][:name], :permitted?
+ ensure
+ ActionController::Parameters.permit_all_parameters = false
end
test "permitting parameters as an array" do
@@ -396,16 +394,14 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "to_h returns converted hash when .permit_all_parameters is set" do
- begin
- ActionController::Parameters.permit_all_parameters = true
- params = ActionController::Parameters.new(crab: "Senjougahara Hitagi")
-
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, params.to_h
- assert_not_kind_of ActionController::Parameters, params.to_h
- assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h)
- ensure
- ActionController::Parameters.permit_all_parameters = false
- end
+ ActionController::Parameters.permit_all_parameters = true
+ params = ActionController::Parameters.new(crab: "Senjougahara Hitagi")
+
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, params.to_h
+ assert_not_kind_of ActionController::Parameters, params.to_h
+ assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h)
+ ensure
+ ActionController::Parameters.permit_all_parameters = false
end
test "to_hash raises UnfilteredParameters on unfiltered params" do
@@ -429,17 +425,15 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "to_hash returns converted hash when .permit_all_parameters is set" do
- begin
- ActionController::Parameters.permit_all_parameters = true
- params = ActionController::Parameters.new(crab: "Senjougahara Hitagi")
-
- assert_instance_of Hash, params.to_hash
- assert_not_kind_of ActionController::Parameters, params.to_hash
- assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_hash)
- assert_equal({ "crab" => "Senjougahara Hitagi" }, params)
- ensure
- ActionController::Parameters.permit_all_parameters = false
- end
+ ActionController::Parameters.permit_all_parameters = true
+ params = ActionController::Parameters.new(crab: "Senjougahara Hitagi")
+
+ assert_instance_of Hash, params.to_hash
+ assert_not_kind_of ActionController::Parameters, params.to_hash
+ assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_hash)
+ assert_equal({ "crab" => "Senjougahara Hitagi" }, params)
+ ensure
+ ActionController::Parameters.permit_all_parameters = false
end
test "to_unsafe_h returns unfiltered params" do
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/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 998498e1b2..7f1c41787a 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -68,10 +68,18 @@ class RedirectController < ActionController::Base
redirect_back(fallback_location: "/things/stuff", status: 307)
end
+ def redirect_back_with_status_and_fallback_location_to_another_host
+ redirect_back(fallback_location: "http://www.rubyonrails.org/", status: 307)
+ end
+
def safe_redirect_back_with_status
redirect_back(fallback_location: "/things/stuff", status: 307, allow_other_host: false)
end
+ def safe_redirect_back_with_status_and_fallback_location_to_another_host
+ redirect_back(fallback_location: "http://www.rubyonrails.org/", status: 307, allow_other_host: false)
+ end
+
def host_redirect
redirect_to action: "other_host", only_path: false, host: "other.test.host"
end
@@ -280,6 +288,13 @@ class RedirectTest < ActionController::TestCase
assert_equal "http://test.host/things/stuff", redirect_to_url
end
+ def test_redirect_back_with_no_referer_redirects_to_another_host
+ get :redirect_back_with_status_and_fallback_location_to_another_host
+
+ assert_response 307
+ assert_equal "http://www.rubyonrails.org/", redirect_to_url
+ end
+
def test_safe_redirect_back_from_other_host
@request.env["HTTP_REFERER"] = "http://another.host/coming/from"
get :safe_redirect_back_with_status
@@ -297,6 +312,20 @@ class RedirectTest < ActionController::TestCase
assert_equal referer, redirect_to_url
end
+ def test_safe_redirect_back_with_no_referer
+ get :safe_redirect_back_with_status
+
+ assert_response 307
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
+ def test_safe_redirect_back_with_no_referer_redirects_to_another_host
+ get :safe_redirect_back_with_status_and_fallback_location_to_another_host
+
+ assert_response 307
+ assert_equal "http://www.rubyonrails.org/", redirect_to_url
+ end
+
def test_redirect_to_record
with_routing do |set|
set.draw do
diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb
index 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 306b245bd1..a2a6c69dd3 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -183,6 +183,11 @@ class TestController < ActionController::Base
render action: "hello_world"
end
+ def conditional_hello_without_expires_and_public_header
+ response.headers["Cache-Control"] = "public, no-cache"
+ render action: "hello_world"
+ end
+
def conditional_hello_with_bangs
render action: "hello_world"
end
@@ -260,7 +265,6 @@ class TestController < ActionController::Base
end
private
-
def set_variable_for_layout
@variable_for_layout = nil
end
@@ -318,11 +322,12 @@ class ExpiresInRenderTest < ActionController::TestCase
end
def test_dynamic_render_with_file
- # This is extremely bad, but should be possible to do.
assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
- response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' }
- assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)),
- response.body
+ assert_deprecated do
+ assert_raises ActionView::MissingTemplate do
+ get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' }
+ end
+ end
end
def test_dynamic_render_with_absolute_path
@@ -346,9 +351,11 @@ class ExpiresInRenderTest < ActionController::TestCase
def test_permitted_dynamic_render_file_hash
assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
- response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } }
- assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)),
- response.body
+ assert_deprecated do
+ assert_raises ActionView::MissingTemplate do
+ get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } }
+ end
+ end
end
def test_dynamic_render_file_hash
@@ -418,6 +425,11 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+ def test_no_expires_now_with_public
+ get :conditional_hello_without_expires_and_public_header
+ assert_equal "public, no-cache", @response.headers["Cache-Control"]
+ end
+
def test_date_header_when_expires_in
time = Time.mktime(2011, 10, 30)
Time.stub :now, time do
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/renderer_test.rb b/actionpack/test/controller/renderer_test.rb
index ae8330e029..ea79f4de85 100644
--- a/actionpack/test/controller/renderer_test.rb
+++ b/actionpack/test/controller/renderer_test.rb
@@ -40,7 +40,7 @@ class RendererTest < ActiveSupport::TestCase
test "rendering with an instance renderer" do
renderer = ApplicationController.renderer.new
- content = renderer.render file: "test/hello_world"
+ content = assert_deprecated { renderer.render file: "test/hello_world" }
assert_equal "Hello world!", content
end
@@ -115,14 +115,14 @@ class RendererTest < ActiveSupport::TestCase
assert_equal "true", content
end
- test "return valid asset url with defaults" do
+ test "return valid asset URL with defaults" do
renderer = ApplicationController.renderer
content = renderer.render inline: "<%= asset_url 'asset.jpg' %>"
assert_equal "http://example.org/asset.jpg", content
end
- test "return valid asset url when https is true" do
+ test "return valid asset URL when https is true" do
renderer = ApplicationController.renderer.new https: true
content = renderer.render inline: "<%= asset_url 'asset.jpg' %>"
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 3c39373e55..538bc15fc9 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -62,14 +62,6 @@ class RescueController < ActionController::Base
render plain: exception.message
end
- rescue_from ActionView::TemplateError do
- render plain: "action_view templater error"
- end
-
- rescue_from IOError do
- render plain: "io error"
- end
-
rescue_from ActionDispatch::Http::Parameters::ParseError do
render plain: "parse error", status: :bad_request
end
@@ -79,19 +71,6 @@ class RescueController < ActionController::Base
def before_action_raises
end
- def raises
- render plain: "already rendered"
- raise "don't panic!"
- end
-
- def method_not_allowed
- raise ActionController::MethodNotAllowed.new(:get, :head, :put)
- end
-
- def not_implemented
- raise ActionController::NotImplemented.new(:get, :put)
- end
-
def not_authorized
raise NotAuthorized
end
@@ -325,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,10 +329,6 @@ class RescueTest < ActionDispatch::IntegrationTest
raise RecordInvalid
end
- def b00m
- raise "b00m"
- end
-
private
def show_errors(exception)
render plain: exception.message
@@ -376,13 +350,11 @@ class RescueTest < ActionDispatch::IntegrationTest
end
private
-
def with_test_routing
with_routing do |set|
set.draw do
get "foo", to: ::RescueTest::TestController.action(:foo)
get "invalid", to: ::RescueTest::TestController.action(:invalid)
- get "b00m", to: ::RescueTest::TestController.action(:b00m)
end
yield
end
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/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 30f2a23b33..b378bb80b8 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -355,10 +355,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
rs.draw { ActiveSupport::Deprecation.silence { get "/:controller/:action", action: /auth[-|_].+/ } }
assert_equal({ action: "auth_google", controller: "content" }, rs.recognize_path("/content/auth_google"))
- assert_equal({ action: "auth-facebook", controller: "content" }, rs.recognize_path("/content/auth-facebook"))
+ assert_equal({ action: "auth-twitter", controller: "content" }, rs.recognize_path("/content/auth-twitter"))
assert_equal "/content/auth_google", url_for(rs, controller: "content", action: "auth_google")
- assert_equal "/content/auth-facebook", url_for(rs, controller: "content", action: "auth-facebook")
+ assert_equal "/content/auth-twitter", url_for(rs, controller: "content", action: "auth-twitter")
end
def test_route_with_regexp_for_controller
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 2094aa1aed..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,22 +91,23 @@ 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)
- @exceptions_app = @app.instance_variable_get(:@exceptions_app)
- @app.instance_variable_set(:@exceptions_app, nil)
+ 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
- @app.instance_variable_set(:@exceptions_app, @exceptions_app)
+ middleware.instance_variable_set(:@exceptions_app, @exceptions_app)
$stderr = STDERR
end
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 6fc70d6248..635a91507d 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -156,12 +156,15 @@ XML
render html: '<body class="foo"></body>'.html_safe
end
+ def render_json
+ render json: request.raw_post
+ end
+
def boom
raise "boom!"
end
private
-
def generate_url(opts)
url_for(opts.merge(action: "test_uri"))
end
@@ -474,6 +477,18 @@ XML
)
end
+ def test_nil_params
+ get :test_params, params: nil
+ parsed_params = JSON.parse(@response.body)
+ assert_equal(
+ {
+ "action" => "test_params",
+ "controller" => "test_case_test/test"
+ },
+ parsed_params
+ )
+ end
+
def test_query_param_named_action
get :test_query_parameters, params: { action: "foobar" }
parsed_params = JSON.parse(@response.body)
@@ -936,7 +951,7 @@ XML
get :create
assert_response :created
- # Redirect url doesn't care that it wasn't a :redirect response.
+ # Redirect URL doesn't care that it wasn't a :redirect response.
assert_equal "/resource", @response.redirect_url
assert_equal @response.redirect_url, redirect_to_url
@@ -965,6 +980,16 @@ XML
assert_equal "q=test2", @response.body
end
+
+ def test_parsed_body_without_as_option
+ post :render_json, body: { foo: "heyo" }
+ assert_equal({ "foo" => "heyo" }, response.parsed_body)
+ end
+
+ def test_parsed_body_with_as_option
+ post :render_json, body: { foo: "heyo" }.to_json, as: :json
+ assert_equal({ "foo" => "heyo" }, response.parsed_body)
+ end
end
class ResponseDefaultHeadersTest < ActionController::TestCase
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index e381abee36..9222250b9c 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -354,6 +354,14 @@ module AbstractController
assert_equal({ p2: "Y2" }.to_query, params[1])
end
+ def test_params_option
+ url = W.new.url_for(only_path: true, controller: "c", action: "a", params: { domain: "foo", id: "1" })
+ params = extract_params(url)
+ assert_equal("/c/a?domain=foo&id=1", url)
+ assert_equal({ domain: "foo" }.to_query, params[0])
+ assert_equal({ id: "1" }.to_query, params[1])
+ end
+
def test_hash_parameter
url = W.new.url_for(only_path: true, controller: "c", action: "a", query: { name: "Bob", category: "prof" })
params = extract_params(url)
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 4a10637b54..23a46df5cd 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -14,7 +14,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
end
def dump_params_keys(hash = params)
- hash.keys.sort.inject("") do |s, k|
+ hash.keys.sort.each_with_object(+"") do |k, s|
value = hash[k]
if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
@@ -23,8 +23,8 @@ class WebServiceTest < ActionDispatch::IntegrationTest
value = ""
end
- s += ", " unless s.empty?
- s += "#{k}#{value}"
+ s << ", " unless s.empty?
+ s << "#{k}#{value}"
end
end
end
diff --git a/actionpack/test/dispatch/actionable_exceptions_test.rb b/actionpack/test/dispatch/actionable_exceptions_test.rb
new file mode 100644
index 0000000000..9215a91e9c
--- /dev/null
+++ b/actionpack/test/dispatch/actionable_exceptions_test.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class ActionableExceptionsTest < ActionDispatch::IntegrationTest
+ Actions = []
+
+ class ActionError < StandardError
+ include ActiveSupport::ActionableError
+
+ action "Successful action" do
+ Actions << "Action!"
+ end
+
+ action "Failed action" do
+ raise "Inaction!"
+ end
+ end
+
+ Noop = -> env { [200, {}, [""]] }
+
+ setup do
+ @app = ActionDispatch::ActionableExceptions.new(Noop)
+
+ Actions.clear
+ end
+
+ test "dispatches an actionable error" do
+ post ActionDispatch::ActionableExceptions.endpoint, params: {
+ error: ActionError.name,
+ action: "Successful action",
+ location: "/",
+ }
+
+ assert_equal ["Action!"], Actions
+
+ assert_equal 302, response.status
+ assert_equal "/", response.headers["Location"]
+ end
+
+ test "cannot dispatch errors if not allowed" do
+ post ActionDispatch::ActionableExceptions.endpoint, params: {
+ error: ActionError.name,
+ action: "Successful action",
+ location: "/",
+ }, headers: { "action_dispatch.show_exceptions" => false }
+
+ assert_empty Actions
+ end
+
+ test "dispatched action can fail" do
+ assert_raise RuntimeError do
+ post ActionDispatch::ActionableExceptions.endpoint, params: {
+ error: ActionError.name,
+ action: "Failed action",
+ location: "/",
+ }
+ end
+ end
+
+ test "cannot dispatch non-actionable errors" do
+ assert_raise ActiveSupport::ActionableError::NonActionable do
+ post ActionDispatch::ActionableExceptions.endpoint, params: {
+ error: RuntimeError.name,
+ action: "Inexistent action",
+ location: "/",
+ }
+ end
+ end
+
+ test "cannot dispatch Inexistent errors" do
+ assert_raise ActiveSupport::ActionableError::NonActionable do
+ post ActionDispatch::ActionableExceptions.endpoint, params: {
+ error: "",
+ action: "Inexistent action",
+ location: "/",
+ }
+ end
+ end
+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/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 6637c2cae9..d129fa717d 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -123,6 +123,11 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ def set_cookie_if_not_present
+ cookies["user_name"] = "alice" unless cookies["user_name"].present?
+ head :ok
+ end
+
def logout
cookies.delete("user_name")
head :ok
@@ -312,7 +317,7 @@ class CookiesTest < ActionController::TestCase
end
def rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_off
- cookies[:favorite] = "Wmg4amgvcVVvWGcwK3c4WjJEbTdRQUgrWXhBdDliUTR0cVNidXpmVTMrc2RjcitwUzVsWWEwZGtuVGtFUjJwNi0tcVhVMTFMOTQ1d0hIVE1FK0pJc05SQT09--8b2a55c375049a50f7a959b9d42b31ef0b2bb594"
+ cookies[:favorite] = "rTG4zs5UufEFAr+ppKwh+MDMymKyAUMOSaWyYa3uUVmD8sMQqyiyQBxgYeAncDHVZIlo4y+kDVSzp66u1/7BNYpnmFe8ES/YT2m8ckNA23jBDmnRZ9CTNfMIRXjFtfxO9YxEOzzhn0ZiA0/zFtr5wkluXtxplOz959Q7MgLOyvTze2h9p8A=--QHOS3rAEGq/HCxXs--xQNra8dk24Idc2qBtpMLpg=="
head :ok
end
@@ -336,7 +341,7 @@ class CookiesTest < ActionController::TestCase
SECRET_KEY_BASE = "b3c631c314c0bbca50c1b2843150fe33"
SIGNED_COOKIE_SALT = "signed cookie"
ENCRYPTED_COOKIE_SALT = "encrypted cookie"
- ENCRYPTED_SIGNED_COOKIE_SALT = "sigend encrypted cookie"
+ ENCRYPTED_SIGNED_COOKIE_SALT = "signed encrypted cookie"
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "authenticated encrypted cookie"
def setup
@@ -525,21 +530,6 @@ class CookiesTest < ActionController::TestCase
assert_equal 45, verifier.verify(@response.cookies["user_id"])
end
- def test_signed_cookie_with_legacy_secret_scheme
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- old_message = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", digest: "SHA1", serializer: Marshal).generate(45)
-
- @request.headers["Cookie"] = "user_id=#{old_message}"
- get :get_signed_cookie
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key("signed cookie")
- verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA1", serializer: Marshal)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
def test_tampered_with_signed_cookie
key_generator = @request.env["action_dispatch.key_generator"]
secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
@@ -759,175 +749,7 @@ class CookiesTest < ActionController::TestCase
assert_equal ["user_name", "user_id"], @request.cookie_jar.instance_variable_get(:@cookies).keys
end
- def test_raises_argument_error_if_missing_secret
- assert_raise(ArgumentError, nil.inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(nil)
- get :set_signed_cookie
- }
-
- assert_raise(ArgumentError, "".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("")
- get :set_signed_cookie
- }
- end
-
- def test_raises_argument_error_if_secret_is_probably_insecure
- assert_raise(ArgumentError, "password".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("password")
- get :set_signed_cookie
- }
-
- assert_raise(ArgumentError, "secret".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("secret")
- get :set_signed_cookie
- }
-
- assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
- @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("12345678901234567890123456789")
- get :set_signed_cookie
- }
- end
-
- def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
-
- @request.headers["Cookie"] = "user_id=#{legacy_value}"
- get :get_signed_cookie
-
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
- verifier = ActiveSupport::MessageVerifier.new(secret)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
- def test_legacy_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar")
-
- @request.headers["Cookie"] = "foo=#{legacy_value}"
- get :get_encrypted_cookie
-
- assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
-
- secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32)
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
- end
-
- def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :json
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
-
- @request.headers["Cookie"] = "user_id=#{legacy_value}"
- get :get_signed_cookie
-
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
- verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
- def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :json
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
-
- @request.headers["Cookie"] = "foo=#{legacy_value}"
- get :get_encrypted_cookie
-
- assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
-
- cipher = "aes-256-gcm"
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
- end
-
- def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :hybrid
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
-
- @request.headers["Cookie"] = "user_id=#{legacy_value}"
- get :get_signed_cookie
-
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
- verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
- def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :hybrid
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
-
- @request.headers["Cookie"] = "foo=#{legacy_value}"
- get :get_encrypted_cookie
-
- assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
-
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
- end
-
- def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :hybrid
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
-
- @request.headers["Cookie"] = "user_id=#{legacy_value}"
- get :get_signed_cookie
-
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
-
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
- verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
- assert_equal 45, verifier.verify(@response.cookies["user_id"])
- end
-
- def test_legacy_marshal_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.cookies_serializer"] = :hybrid
-
- @request.env["action_dispatch.use_authenticated_cookie_encryption"] = true
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
-
- legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar")
-
- @request.headers["Cookie"] = "foo=#{legacy_value}"
- get :get_encrypted_cookie
-
- assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
-
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
- end
-
def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
@request.headers["Cookie"] = "user_id=45"
get :get_signed_cookie
@@ -936,8 +758,6 @@ class CookiesTest < ActionController::TestCase
end
def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
-
@request.headers["Cookie"] = "foo=baz"
get :get_encrypted_cookie
@@ -1313,6 +1133,14 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.encrypted_cookie
end
+ def test_cookie_override
+ get :set_cookie_if_not_present
+ assert_equal "alice", cookies["user_name"]
+ cookies["user_name"] = "bob"
+ get :set_cookie_if_not_present
+ assert_equal "bob", cookies["user_name"]
+ end
+
def test_signed_cookie_with_expires_set_relatively
request.env["action_dispatch.use_cookies_with_metadata"] = true
@@ -1378,11 +1206,7 @@ class CookiesTest < ActionController::TestCase
get :encrypted_discount_and_user_id_cookie
travel 2.hours
- assert_equal 50, cookies.encrypted[:user_id]
-
- cookies[:discount_percentage] = cookies[:user_id]
- assert_not_equal 10, cookies.encrypted[:discount_percentage]
- assert_equal 50, cookies.encrypted[:discount_percentage]
+ assert_nil cookies.signed[:user_id]
end
def test_switch_off_metadata_for_signed_cookies_if_config_is_false
@@ -1391,11 +1215,8 @@ class CookiesTest < ActionController::TestCase
get :signed_discount_and_user_id_cookie
travel 2.hours
- assert_equal 50, cookies.signed[:user_id]
- cookies[:discount_percentage] = cookies[:user_id]
- assert_not_equal 10, cookies.signed[:discount_percentage]
- assert_equal 50, cookies.signed[:discount_percentage]
+ assert_nil cookies.signed[:user_id]
end
def test_read_rails_5_2_stable_encrypted_cookies_if_config_is_false
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 37399cfd07..fa629bc761 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -5,10 +5,22 @@ require "abstract_unit"
class DebugExceptionsTest < ActionDispatch::IntegrationTest
InterceptedErrorInstance = StandardError.new
+ class CustomActionableError < StandardError
+ include ActiveSupport::ActionableError
+
+ action "Action 1" do
+ nil
+ end
+
+ action "Action 2" do
+ nil
+ end
+ end
+
class Boomer
attr_accessor :closed
- def initialize(detailed = false)
+ def initialize(detailed = false)
@detailed = detailed
@closed = false
end
@@ -27,67 +39,73 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
def raise_nested_exceptions
+ raise "First error"
+ rescue
begin
- raise "First error"
+ raise "Second error"
rescue
- begin
- raise "Second error"
- rescue
- raise "Third error"
- end
+ raise "Third error"
end
end
def call(env)
env["action_dispatch.show_detailed_exceptions"] = @detailed
req = ActionDispatch::Request.new(env)
+ template = ActionView::Template.new(File.read(__FILE__), __FILE__, ActionView::Template::Handlers::Raw.new, format: :html, locals: [])
+
case req.path
- when %r{/pass}
+ when "/pass"
[404, { "X-Cascade" => "pass" }, self]
- when %r{/not_found}
+ when "/not_found"
raise AbstractController::ActionNotFound
- when %r{/runtime_error}
+ when "/runtime_error"
raise RuntimeError
- when %r{/method_not_allowed}
+ when "/method_not_allowed"
raise ActionController::MethodNotAllowed
- when %r{/intercepted_error}
+ when "/intercepted_error"
raise InterceptedErrorInstance
- when %r{/unknown_http_method}
+ when "/unknown_http_method"
raise ActionController::UnknownHttpMethod
- when %r{/not_implemented}
+ when "/not_implemented"
raise ActionController::NotImplemented
- when %r{/unprocessable_entity}
+ when "/unprocessable_entity"
raise ActionController::InvalidAuthenticityToken
- when %r{/not_found_original_exception}
+ when "/invalid_mimetype"
+ raise Mime::Type::InvalidMimeType
+ when "/not_found_original_exception"
begin
raise AbstractController::ActionNotFound.new
rescue
- raise ActionView::Template::Error.new("template")
+ raise ActionView::Template::Error.new(template)
+ end
+ when "/cause_mapped_to_rescue_responses"
+ begin
+ raise ActionController::ParameterMissing, :missing_param_key
+ rescue
+ raise NameError.new("uninitialized constant Userr")
end
- when %r{/missing_template}
+ when "/missing_template"
raise ActionView::MissingTemplate.new(%w(foo), "foo/index", %w(foo), false, "mailer")
- when %r{/bad_request}
+ when "/bad_request"
raise ActionController::BadRequest
- when %r{/missing_keys}
+ when "/missing_keys"
raise ActionController::UrlGenerationError, "No route matches"
- when %r{/parameter_missing}
+ when "/parameter_missing"
raise ActionController::ParameterMissing, :missing_param_key
- when %r{/original_syntax_error}
+ when "/original_syntax_error"
eval "broke_syntax =" # `eval` need for raise native SyntaxError at runtime
- when %r{/syntax_error_into_view}
+ when "/syntax_error_into_view"
begin
eval "broke_syntax ="
rescue Exception
- template = ActionView::Template.new(File.read(__FILE__),
- __FILE__,
- ActionView::Template::Handlers::Raw.new,
- {})
raise ActionView::Template::Error.new(template)
end
- when %r{/framework_raises}
+ when "/framework_raises"
method_that_raises
- when %r{/nested_exceptions}
+ when "/nested_exceptions"
raise_nested_exceptions
+ when %r{/actionable_error}
+ raise CustomActionableError
else
raise "puke!"
end
@@ -176,6 +194,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/parameter_missing", headers: { "action_dispatch.show_exceptions" => true }
assert_response 400
assert_match(/ActionController::ParameterMissing/, body)
+
+ get "/invalid_mimetype", headers: { "Accept" => "text/html,*", "action_dispatch.show_exceptions" => true }
+ assert_response 406
+ assert_match(/Mime::Type::InvalidMimeType/, body)
end
test "rescue with text error for xhr request" do
@@ -186,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
@@ -200,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
@@ -235,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
@@ -276,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
@@ -285,27 +307,25 @@ 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
test "rescue with JSON format as fallback if API request format is not supported" do
- begin
- Mime::Type.register "text/wibble", :wibble
+ Mime::Type.register "text/wibble", :wibble
- ActionDispatch::IntegrationTest.register_encoder(:wibble,
- param_encoder: -> params { params })
+ ActionDispatch::IntegrationTest.register_encoder(:wibble,
+ param_encoder: -> params { params })
- @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api)
+ @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api)
- get "/index", headers: { "action_dispatch.show_exceptions" => true }, as: :wibble
- assert_response 500
- assert_equal "application/json", response.content_type
- assert_match(/RuntimeError: puke/, body)
+ get "/index", headers: { "action_dispatch.show_exceptions" => true }, as: :wibble
+ assert_response 500
+ assert_equal "application/json", response.media_type
+ assert_match(/RuntimeError: puke/, body)
- ensure
- Mime::Type.unregister :wibble
- end
+ ensure
+ Mime::Type.unregister :wibble
end
test "does not show filtered parameters" do
@@ -317,15 +337,25 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
end
- test "show registered original exception for wrapped exceptions" do
+ test "show registered original exception if the last exception is TemplateError" do
@app = DevelopmentApp
get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true }
assert_response 404
- assert_match(/AbstractController::ActionNotFound/, body)
+ assert_match %r{AbstractController::ActionNotFound}, body
+ assert_match %r{Showing <i>.*test/dispatch/debug_exceptions_test.rb</i>}, body
end
- test "named urls missing keys raise 500 level error" do
+ test "show the last exception and cause even when the cause is mapped to resque_responses" do
+ @app = DevelopmentApp
+
+ get "/cause_mapped_to_rescue_responses", headers: { "action_dispatch.show_exceptions" => true }
+ assert_response 500
+ assert_match %r{ActionController::ParameterMissing}, body
+ assert_match %r{NameError}, body
+ end
+
+ test "named URLs missing keys raise 500 level error" do
@app = DevelopmentApp
get "/missing_keys", headers: { "action_dispatch.show_exceptions" => true }
@@ -436,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 }
@@ -448,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
@@ -484,6 +537,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_select "#Application-Trace-0" do
assert_select "code", /syntax error, unexpected/
end
+ assert_match %r{Showing <i>.*test/dispatch/debug_exceptions_test.rb</i>}, body
end
test "debug exceptions app shows user code that caused the error in source view" do
@@ -572,4 +626,40 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
end
end
+
+ test "shows a buttons for every action in an actionable error" do
+ @app = DevelopmentApp
+ Rails.stub :root, Pathname.new(".") do
+ cleaner = ActiveSupport::BacktraceCleaner.new.tap do |bc|
+ bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} }
+ end
+
+ get "/actionable_error", headers: { "action_dispatch.backtrace_cleaner" => cleaner }
+
+ # Assert correct error
+ assert_response 500
+
+ assert_select 'input[value="Action 1"]'
+ 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/host_authorization_test.rb b/actionpack/test/dispatch/host_authorization_test.rb
new file mode 100644
index 0000000000..5263dd2597
--- /dev/null
+++ b/actionpack/test/dispatch/host_authorization_test.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "ipaddr"
+
+class HostAuthorizationTest < ActionDispatch::IntegrationTest
+ App = -> env { [200, {}, %w(Success)] }
+
+ test "blocks requests to unallowed host" do
+ @app = ActionDispatch::HostAuthorization.new(App, %w(only.com))
+
+ get "/"
+
+ assert_response :forbidden
+ assert_match "Blocked host: www.example.com", response.body
+ end
+
+ test "allows all requests if hosts is empty" do
+ @app = ActionDispatch::HostAuthorization.new(App, nil)
+
+ get "/"
+
+ assert_response :ok
+ assert_equal "Success", body
+ end
+
+ test "hosts can be a single element array" do
+ @app = ActionDispatch::HostAuthorization.new(App, %w(www.example.com))
+
+ get "/"
+
+ assert_response :ok
+ assert_equal "Success", body
+ end
+
+ test "hosts can be a string" do
+ @app = ActionDispatch::HostAuthorization.new(App, "www.example.com")
+
+ get "/"
+
+ assert_response :ok
+ assert_equal "Success", body
+ end
+
+ test "passes requests to allowed hosts with domain name notation" do
+ @app = ActionDispatch::HostAuthorization.new(App, ".example.com")
+
+ get "/"
+
+ assert_response :ok
+ assert_equal "Success", body
+ end
+
+ test "does not allow domain name notation in the HOST header itself" do
+ @app = ActionDispatch::HostAuthorization.new(App, ".example.com")
+
+ get "/", env: {
+ "HOST" => ".example.com",
+ }
+
+ assert_response :forbidden
+ assert_match "Blocked host: .example.com", response.body
+ end
+
+ test "checks for requests with #=== to support wider range of host checks" do
+ @app = ActionDispatch::HostAuthorization.new(App, [-> input { input == "www.example.com" }])
+
+ get "/"
+
+ assert_response :ok
+ assert_equal "Success", body
+ end
+
+ test "mark the host when authorized" do
+ @app = ActionDispatch::HostAuthorization.new(App, ".example.com")
+
+ get "/"
+
+ assert_equal "www.example.com", request.get_header("action_dispatch.authorized_host")
+ end
+
+ test "sanitizes regular expressions to prevent accidental matches" do
+ @app = ActionDispatch::HostAuthorization.new(App, [/w.example.co/])
+
+ get "/"
+
+ assert_response :forbidden
+ assert_match "Blocked host: www.example.com", response.body
+ end
+
+ test "blocks requests to unallowed host supporting custom responses" do
+ @app = ActionDispatch::HostAuthorization.new(App, ["w.example.co"], -> env do
+ [401, {}, %w(Custom)]
+ end)
+
+ get "/"
+
+ assert_response :unauthorized
+ assert_equal "Custom", body
+ end
+
+ test "blocks requests with spoofed X-FORWARDED-HOST" do
+ @app = ActionDispatch::HostAuthorization.new(App, [IPAddr.new("127.0.0.1")])
+
+ get "/", env: {
+ "HTTP_X_FORWARDED_HOST" => "127.0.0.1",
+ "HOST" => "www.example.com",
+ }
+
+ assert_response :forbidden
+ assert_match "Blocked host: 127.0.0.1", response.body
+ end
+
+ test "does not consider IP addresses in X-FORWARDED-HOST spoofed when disabled" do
+ @app = ActionDispatch::HostAuthorization.new(App, nil)
+
+ get "/", env: {
+ "HTTP_X_FORWARDED_HOST" => "127.0.0.1",
+ "HOST" => "www.example.com",
+ }
+
+ assert_response :ok
+ assert_equal "Success", body
+ end
+
+ test "detects localhost domain spoofing" do
+ @app = ActionDispatch::HostAuthorization.new(App, "localhost")
+
+ get "/", env: {
+ "HTTP_X_FORWARDED_HOST" => "localhost",
+ "HOST" => "www.example.com",
+ }
+
+ assert_response :forbidden
+ assert_match "Blocked host: localhost", response.body
+ end
+
+ test "forwarded hosts should be permitted" do
+ @app = ActionDispatch::HostAuthorization.new(App, "domain.com")
+
+ get "/", env: {
+ "HTTP_X_FORWARDED_HOST" => "sub.domain.com",
+ "HOST" => "domain.com",
+ }
+
+ assert_response :forbidden
+ assert_match "Blocked host: sub.domain.com", response.body
+ end
+
+ test "forwarded hosts are allowed when permitted" do
+ @app = ActionDispatch::HostAuthorization.new(App, ".domain.com")
+
+ get "/", env: {
+ "HTTP_X_FORWARDED_HOST" => "sub.domain.com",
+ "HOST" => "domain.com",
+ }
+
+ assert_response :ok
+ assert_equal "Success", body
+ end
+end
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index a9a56f205f..f2459112b2 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -51,18 +51,24 @@ module ActionController
assert_equal ["omg"], @response.body_parts
end
- def test_cache_control_is_set
+ def test_cache_control_is_set_by_default
@response.stream.write "omg"
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+ def test_cache_control_is_set_manually
+ @response.set_header("Cache-Control", "public")
+ @response.stream.write "omg"
+ assert_equal "public", @response.headers["Cache-Control"]
+ end
+
def test_content_length_is_removed
@response.headers["Content-Length"] = "1234"
@response.stream.write "omg"
assert_nil @response.headers["Content-Length"]
end
- def test_headers_cannot_be_written_after_webserver_reads
+ def test_headers_cannot_be_written_after_web_server_reads
@response.stream.write "omg"
latch = Concurrent::CountDownLatch.new
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 5f43e5a3c5..c534e60c74 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -3,13 +3,24 @@
require "abstract_unit"
class MiddlewareStackTest < ActiveSupport::TestCase
- class FooMiddleware; end
- class BarMiddleware; end
- class BazMiddleware; end
- class HiyaMiddleware; end
- class BlockMiddleware
+ class Base
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ @app.call(env)
+ end
+ end
+
+ class FooMiddleware < Base; end
+ class BarMiddleware < Base; end
+ class BazMiddleware < Base; end
+ class HiyaMiddleware < Base; end
+ class BlockMiddleware < Base
attr_reader :block
- def initialize(&block)
+ def initialize(app, &block)
+ super(app)
@block = block
end
end
@@ -109,6 +120,24 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal @stack.last, @stack.last
end
+ test "instruments the execution of middlewares" do
+ events = []
+
+ subscriber = proc do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ ActiveSupport::Notifications.subscribed(subscriber, "process_middleware.action_dispatch") do
+ app = @stack.build(proc { |env| [200, {}, []] })
+
+ env = {}
+ app.call(env)
+ end
+
+ assert_equal 2, events.count
+ assert_equal ["MiddlewareStackTest::BarMiddleware", "MiddlewareStackTest::FooMiddleware"], events.map { |e| e.payload[:middleware] }
+ end
+
test "includes a middleware" do
assert_equal true, @stack.include?(ActionDispatch::MiddlewareStack::Middleware.new(BarMiddleware, nil, nil))
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index fa264417e1..50f6c06fee 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -96,57 +96,47 @@ class MimeTypeTest < ActiveSupport::TestCase
end
test "custom type" do
- begin
- type = Mime::Type.register("image/foo", :foo)
- assert_equal type, Mime[:foo]
- ensure
- Mime::Type.unregister(:foo)
- end
+ type = Mime::Type.register("image/foo", :foo)
+ assert_equal type, Mime[:foo]
+ ensure
+ Mime::Type.unregister(:foo)
end
test "custom type with type aliases" do
- begin
- Mime::Type.register "text/foobar", :foobar, ["text/foo", "text/bar"]
- %w[text/foobar text/foo text/bar].each do |type|
- assert_equal Mime[:foobar], type
- end
- ensure
- Mime::Type.unregister(:foobar)
+ Mime::Type.register "text/foobar", :foobar, ["text/foo", "text/bar"]
+ %w[text/foobar text/foo text/bar].each do |type|
+ assert_equal Mime[:foobar], type
end
+ ensure
+ Mime::Type.unregister(:foobar)
end
test "register callbacks" do
- begin
- registered_mimes = []
- Mime::Type.register_callback do |mime|
- registered_mimes << mime
- end
-
- mime = Mime::Type.register("text/foo", :foo)
- assert_equal [mime], registered_mimes
- ensure
- Mime::Type.unregister(:foo)
+ registered_mimes = []
+ Mime::Type.register_callback do |mime|
+ registered_mimes << mime
end
+
+ mime = Mime::Type.register("text/foo", :foo)
+ assert_equal [mime], registered_mimes
+ ensure
+ Mime::Type.unregister(:foo)
end
test "custom type with extension aliases" do
- begin
- Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"]
- %w[foobar foo bar].each do |extension|
- assert_equal Mime[:foobar], Mime::EXTENSION_LOOKUP[extension]
- end
- ensure
- Mime::Type.unregister(:foobar)
+ Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"]
+ %w[foobar foo bar].each do |extension|
+ assert_equal Mime[:foobar], Mime::EXTENSION_LOOKUP[extension]
end
+ ensure
+ Mime::Type.unregister(:foobar)
end
test "register alias" do
- begin
- Mime::Type.register_alias "application/xhtml+xml", :foobar
- assert_equal Mime[:html], Mime::EXTENSION_LOOKUP["foobar"]
- ensure
- Mime::Type.unregister(:foobar)
- end
+ Mime::Type.register_alias "application/xhtml+xml", :foobar
+ assert_equal Mime[:html], Mime::EXTENSION_LOOKUP["foobar"]
+ ensure
+ Mime::Type.unregister(:foobar)
end
test "type should be equal to symbol" do
@@ -184,4 +174,51 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_not (Mime[:js] !~ "application/javascript")
assert Mime[:html] =~ "application/xhtml+xml"
end
+
+ test "can be initialized with wildcards" do
+ assert_equal "*/*", Mime::Type.new("*/*").to_s
+ assert_equal "text/*", Mime::Type.new("text/*").to_s
+ assert_equal "video/*", Mime::Type.new("video/*").to_s
+ end
+
+ test "can be initialized with parameters" do
+ assert_equal "text/html; parameter", Mime::Type.new("text/html; parameter").to_s
+ assert_equal "text/html; parameter=abc", Mime::Type.new("text/html; parameter=abc").to_s
+ assert_equal 'text/html; parameter="abc"', Mime::Type.new('text/html; parameter="abc"').to_s
+ assert_equal 'text/html; parameter=abc; parameter2="xyz"', Mime::Type.new('text/html; parameter=abc; parameter2="xyz"').to_s
+ end
+
+ test "invalid mime types raise error" do
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("too/many/slash")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("missingslash")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("improper/semicolon;")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new('improper/semicolon; parameter=abc; parameter2="xyz";')
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("text/html, text/plain")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("*/html")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new("")
+ end
+
+ assert_raises Mime::Type::InvalidMimeType do
+ Mime::Type.new(nil)
+ end
+ end
end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index f6cf653980..758cee9930 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -27,6 +27,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
}
mount SprocketsApp, at: "/sprockets"
+ mount SprocketsApp, at: "/star*"
mount SprocketsApp => "/shorthand"
mount SinatraLikeApp, at: "/fakeengine", as: :fake
@@ -58,6 +59,14 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
def test_mounting_at_root_path
get "/omg"
assert_equal " -- /omg", response.body
+
+ get "/~omg"
+ assert_equal " -- /~omg", response.body
+ end
+
+ def test_mounting_at_path_with_non_word_character
+ get "/star*/omg"
+ assert_equal "/star* -- /omg", response.body
end
def test_mounting_sets_script_name
@@ -80,6 +89,12 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
assert_equal "/shorthand -- /omg", response.body
end
+ def test_mounting_does_not_match_similar_paths
+ get "/shorthandomg"
+ assert_not_equal "/shorthand -- /omg", response.body
+ assert_equal " -- /shorthandomg", response.body
+ end
+
def test_mounting_works_with_via
get "/getfake"
assert_equal "OK", response.body
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index 7a7a201b11..63c147cb1b 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -151,17 +151,17 @@ module TestGenerationPrefix
include BlogEngine.routes.mounted_helpers
# Inside Engine
- test "[ENGINE] generating engine's url use SCRIPT_NAME from request" do
+ test "[ENGINE] generating engine's URL use SCRIPT_NAME from request" do
get "/pure-awesomeness/blog/posts/1"
assert_equal "/pure-awesomeness/blog/posts/1", response.body
end
- test "[ENGINE] generating application's url never uses SCRIPT_NAME from request" do
+ test "[ENGINE] generating application's URL never uses SCRIPT_NAME from request" do
get "/pure-awesomeness/blog/url_to_application"
assert_equal "/generate", response.body
end
- test "[ENGINE] generating engine's url with polymorphic path" do
+ test "[ENGINE] generating engine's URL with polymorphic path" do
get "/pure-awesomeness/blog/polymorphic_path_for_engine"
assert_equal "/pure-awesomeness/blog/posts/1", response.body
end
@@ -243,7 +243,7 @@ module TestGenerationPrefix
assert_equal "/something/awesome/blog/posts/1", response.body
end
- test "[APP] generating engine's url with polymorphic path" do
+ test "[APP] generating engine's URL with polymorphic path" do
get "/polymorphic_path_for_engine"
assert_equal "/awesome/blog/posts/1", response.body
end
@@ -253,7 +253,7 @@ module TestGenerationPrefix
assert_equal "/posts/1", response.body
end
- test "[APP] generating engine's url with url_for(@post)" do
+ test "[APP] generating engine's URL with url_for(@post)" do
get "/polymorphic_with_url_for"
assert_equal "http://www.example.com/awesome/blog/posts/1", response.body
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index beab8e78b5..2a48a12497 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -74,17 +74,15 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test "occurring a parse error if parsing unsuccessful" do
with_test_routing do
- begin
- $stderr = StringIO.new # suppress the log
- json = "[\"person]\": {\"name\": \"David\"}}"
- exception = assert_raise(ActionDispatch::Http::Parameters::ParseError) do
- post "/parse", params: json, headers: { "CONTENT_TYPE" => "application/json", "action_dispatch.show_exceptions" => false }
- end
- assert_equal JSON::ParserError, exception.cause.class
- assert_equal exception.cause.message, exception.message
- ensure
- $stderr = STDERR
+ $stderr = StringIO.new # suppress the log
+ json = "[\"person]\": {\"name\": \"David\"}}"
+ exception = assert_raise(ActionDispatch::Http::Parameters::ParseError) do
+ post "/parse", params: json, headers: { "CONTENT_TYPE" => "application/json", "action_dispatch.show_exceptions" => false }
end
+ assert_equal JSON::ParserError, exception.cause.class
+ assert_equal exception.cause.message, exception.message
+ ensure
+ $stderr = STDERR
end
end
@@ -157,31 +155,27 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
end
test "parses json params after custom json mime type registered" do
- begin
- Mime::Type.unregister :json
- Mime::Type.register "application/json", :json, %w(application/vnd.rails+json)
- assert_parses(
- { "user" => { "username" => "meinac" }, "username" => "meinac" },
- "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/json"
- )
- ensure
- Mime::Type.unregister :json
- Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
- end
+ Mime::Type.unregister :json
+ Mime::Type.register "application/json", :json, %w(application/vnd.rails+json)
+ assert_parses(
+ { "user" => { "username" => "meinac" }, "username" => "meinac" },
+ "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/json"
+ )
+ ensure
+ Mime::Type.unregister :json
+ Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
end
test "parses json params after custom json mime type registered with synonym" do
- begin
- Mime::Type.unregister :json
- Mime::Type.register "application/json", :json, %w(application/vnd.rails+json)
- assert_parses(
- { "user" => { "username" => "meinac" }, "username" => "meinac" },
- "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/vnd.rails+json"
- )
- ensure
- Mime::Type.unregister :json
- Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
- end
+ Mime::Type.unregister :json
+ Mime::Type.register "application/json", :json, %w(application/vnd.rails+json)
+ assert_parses(
+ { "user" => { "username" => "meinac" }, "username" => "meinac" },
+ "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/vnd.rails+json"
+ )
+ ensure
+ Mime::Type.unregister :json
+ Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
end
private
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 9d1246b3a4..0ec8dd25e0 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -411,7 +411,7 @@ class RequestPath < BaseRequestTest
assert_equal "/foo?bar", path
end
- test "original_url returns url built using ORIGINAL_FULLPATH" do
+ test "original_url returns URL built using ORIGINAL_FULLPATH" do
request = stub_request("ORIGINAL_FULLPATH" => "/foo?bar",
"HTTP_HOST" => "example.org",
"rack.url_scheme" => "http")
@@ -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)
@@ -763,7 +762,6 @@ class RequestMethod < BaseRequestTest
test "post uneffected by local inflections" do
existing_acronyms = ActiveSupport::Inflector.inflections.acronyms.dup
- assert_deprecated { ActiveSupport::Inflector.inflections.acronym_regex.dup }
begin
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym "POS"
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 0f37d074af..ed64d89902 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -42,7 +42,7 @@ class ResponseTest < ActiveSupport::TestCase
def test_each_isnt_called_if_str_body_is_written
# Controller writes and reads response body
each_counter = 0
- @response.body = Object.new.tap { |o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call "foo" } }
+ @response.body = Object.new.tap { |o| o.singleton_class.define_method(:each) { |&block| each_counter += 1; block.call "foo" } }
@response["X-Foo"] = @response.body
assert_equal 1, each_counter, "#each was not called once"
@@ -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
@@ -539,4 +539,87 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers["ETag"])
assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
end
+
+ test "response Content-Type with optional parameters" do
+ @app = lambda { |env|
+ [
+ 200,
+ { "Content-Type" => "text/csv; charset=utf-16; header=present" },
+ ["Hello"]
+ ]
+ }
+
+ get "/"
+ assert_response :success
+
+ assert_equal("text/csv; charset=utf-16; header=present", @response.headers["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
+
+ test "response Content-Type with quoted-string" 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
+
+ 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/non_dispatch_routed_app_test.rb b/actionpack/test/dispatch/routing/non_dispatch_routed_app_test.rb
new file mode 100644
index 0000000000..676a8c38d4
--- /dev/null
+++ b/actionpack/test/dispatch/routing/non_dispatch_routed_app_test.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+module ActionDispatch
+ module Routing
+ class NonDispatchRoutedAppTest < ActionDispatch::IntegrationTest
+ # For example, Grape::API
+ class SimpleApp
+ def self.call(env)
+ [ 200, { "Content-Type" => "text/plain" }, [] ]
+ end
+
+ def self.routes
+ []
+ end
+ end
+
+ setup { @app = SimpleApp }
+
+ test "does not except" do
+ get "/foo"
+ assert_response :success
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
index e61d47b160..e6a2c35798 100644
--- a/actionpack/test/dispatch/routing/route_set_test.rb
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -29,7 +29,7 @@ module ActionDispatch
assert_not empty?
end
- test "url helpers are added when route is added" do
+ test "URL helpers are added when route is added" do
draw do
get "foo", to: SimpleApp.new("foo#index")
end
@@ -48,7 +48,7 @@ module ActionDispatch
assert_equal "/bar", url_helpers.bar_path
end
- test "url helpers are updated when route is updated" do
+ test "URL helpers are updated when route is updated" do
draw do
get "bar", to: SimpleApp.new("bar#index"), as: :bar
end
@@ -62,7 +62,7 @@ module ActionDispatch
assert_equal "/baz", url_helpers.bar_path
end
- test "url helpers are removed when route is removed" do
+ test "URL helpers are removed when route is removed" do
draw do
get "foo", to: SimpleApp.new("foo#index")
get "bar", to: SimpleApp.new("bar#index")
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index affc2d8497..b67b1dd347 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2200,6 +2200,37 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal "cards#destroy", @response.body
end
+ def test_shallow_false_inside_nested_shallow_resource
+ draw do
+ resources :blogs, shallow: true do
+ resources :posts do
+ resources :comments, shallow: false
+ resources :tags
+ end
+ end
+ end
+
+ get "/posts/1/comments"
+ assert_equal "comments#index", @response.body
+ assert_equal "/posts/1/comments", post_comments_path("1")
+
+ get "/posts/1/comments/new"
+ assert_equal "comments#new", @response.body
+ assert_equal "/posts/1/comments/new", new_post_comment_path("1")
+
+ get "/posts/1/comments/2"
+ assert_equal "comments#show", @response.body
+ assert_equal "/posts/1/comments/2", post_comment_path("1", "2")
+
+ get "/posts/1/comments/2/edit"
+ assert_equal "comments#edit", @response.body
+ assert_equal "/posts/1/comments/2/edit", edit_post_comment_path("1", "2")
+
+ get "/tags/3"
+ assert_equal "tags#show", @response.body
+ assert_equal "/tags/3", tag_path("3")
+ end
+
def test_shallow_deeply_nested_resources
draw do
resources :blogs do
@@ -3338,13 +3369,23 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal "0c0c0b68-d24b-11e1-a861-001ff3fffe6f", @request.params[:download]
end
- def test_action_from_path_is_not_frozen
+ def test_colon_containing_custom_param
+ ex = assert_raises(ArgumentError) {
+ draw do
+ resources :profiles, param: "username/:is_admin"
+ end
+ }
+
+ assert_match(/:param option can't contain colon/, ex.message)
+ end
+
+ def test_action_from_path_is_frozen
draw do
get "search" => "search"
end
get "/search"
- assert_not_predicate @request.params[:action], :frozen?
+ assert_predicate @request.params[:action], :frozen?
end
def test_multiple_positional_args_with_the_same_name
@@ -3698,15 +3739,25 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- def test_multiple_roots
+ def test_multiple_roots_raises_error
+ ex = assert_raises(ArgumentError) {
+ draw do
+ root "pages#index", constraints: { host: "www.example.com" }
+ root "admin/pages#index", constraints: { host: "admin.example.com" }
+ end
+ }
+ assert_match(/Invalid route name, already in use: 'root'/, ex.message)
+ end
+
+ def test_multiple_named_roots
draw do
namespace :foo do
root "pages#index", constraints: { host: "www.example.com" }
- root "admin/pages#index", constraints: { host: "admin.example.com" }
+ root "admin/pages#index", constraints: { host: "admin.example.com" }, as: :admin_root
end
root "pages#index", constraints: { host: "www.example.com" }
- root "admin/pages#index", constraints: { host: "admin.example.com" }
+ root "admin/pages#index", constraints: { host: "admin.example.com" }, as: :admin_root
end
get "http://www.example.com/foo"
@@ -3759,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" }
@@ -4372,7 +4422,7 @@ class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
include Routes.url_helpers
- test "url helpers do not ignore nil parameters when using non-optimized routes" do
+ test "URL helpers do not ignore nil parameters when using non-optimized routes" do
Routes.stub :optimize_routes_generation?, false do
get "/categories/1"
assert_response :success
@@ -4744,7 +4794,7 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
include Routes.url_helpers
- test "url helpers raise a 'missing keys' error for a nil param with optimized helpers" do
+ test "URL helpers raise a 'missing keys' error for a nil param with optimized helpers" do
url, missing = { action: "show", controller: "products", id: nil }, [:id]
message = "No route matches #{url.inspect}, missing required keys: #{missing.inspect}"
@@ -4752,7 +4802,7 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
assert_equal message, error.message
end
- test "url helpers raise a 'constraint failure' error for a nil param with non-optimized helpers" do
+ test "URL helpers raise a 'constraint failure' error for a nil param with non-optimized helpers" do
url, missing = { action: "show", controller: "products", id: nil }, [:id]
message = "No route matches #{url.inspect}, possible unmatched constraints: #{missing.inspect}"
@@ -4760,15 +4810,15 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
assert_equal message, error.message
end
- test "url helpers raise message with mixed parameters when generation fails" do
+ test "URL helpers raise message with mixed parameters when generation fails" do
url, missing = { action: "show", controller: "products", id: nil, "id" => "url-tested" }, [:id]
message = "No route matches #{url.inspect}, possible unmatched constraints: #{missing.inspect}"
- # Optimized url helper
+ # Optimized URL helper
error = assert_raises(ActionController::UrlGenerationError) { product_path(nil, "id" => "url-tested") }
assert_equal message, error.message
- # Non-optimized url helper
+ # Non-optimized URL helper
error = assert_raises(ActionController::UrlGenerationError, message) { product_path(id: nil, "id" => "url-tested") }
assert_equal message, error.message
end
@@ -4902,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
@@ -4981,8 +5071,12 @@ end
class FlashRedirectTest < ActionDispatch::IntegrationTest
SessionKey = "_myapp_session"
- Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
- Rotations = ActiveSupport::Messages::RotationConfiguration.new
+ Generator = ActiveSupport::CachingKeyGenerator.new(
+ ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000)
+ )
+ Rotations = ActiveSupport::Messages::RotationConfiguration.new
+ SIGNED_COOKIE_SALT = "signed cookie"
+ ENCRYPTED_SIGNED_COOKIE_SALT = "signed encrypted cookie"
class KeyGeneratorMiddleware
def initialize(app)
@@ -4992,6 +5086,8 @@ class FlashRedirectTest < ActionDispatch::IntegrationTest
def call(env)
env["action_dispatch.key_generator"] ||= Generator
env["action_dispatch.cookies_rotations"] ||= Rotations
+ env["action_dispatch.signed_cookie_salt"] = SIGNED_COOKIE_SALT
+ env["action_dispatch.encrypted_signed_cookie_salt"] = ENCRYPTED_SIGNED_COOKIE_SALT
@app.call(env)
end
@@ -5086,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/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 9b51ee1cad..ac685a7dca 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -38,8 +38,9 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
begin
require "dalli"
- ss = Dalli::Client.new("localhost:11211").stats
- raise Dalli::DalliError unless ss["localhost:11211"]
+ servers = ENV["MEMCACHE_SERVERS"] || "localhost:11211"
+ ss = Dalli::Client.new(servers).stats
+ raise Dalli::DalliError unless ss[servers]
def test_setting_and_getting_session_value
with_test_route_set do
@@ -195,7 +196,9 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
end
@app = self.class.build_app(set) do |middleware|
- middleware.use ActionDispatch::Session::MemCacheStore, key: "_session_id", namespace: "mem_cache_store_test:#{SecureRandom.hex(10)}"
+ middleware.use ActionDispatch::Session::MemCacheStore,
+ key: "_session_id", namespace: "mem_cache_store_test:#{SecureRandom.hex(10)}",
+ memcache_server: ENV["MEMCACHE_SERVERS"] || "localhost:11211"
middleware.delete ActionDispatch::ShowExceptions
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index b69071b44b..6fafa4e426 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -9,6 +9,8 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
case req.path
when "/not_found"
raise AbstractController::ActionNotFound
+ when "/invalid_mimetype"
+ raise Mime::Type::InvalidMimeType
when "/bad_params", "/bad_params.json"
begin
raise StandardError.new
@@ -36,32 +38,36 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
test "skip exceptions app if not showing exceptions" do
@app = ProductionApp
assert_raise RuntimeError do
- get "/", headers: { "action_dispatch.show_exceptions" => false }
+ get "/", env: { "action_dispatch.show_exceptions" => false }
end
end
test "rescue with error page" do
@app = ProductionApp
- get "/", headers: { "action_dispatch.show_exceptions" => true }
+ get "/", env: { "action_dispatch.show_exceptions" => true }
assert_response 500
assert_equal "500 error fixture\n", body
- get "/bad_params", headers: { "action_dispatch.show_exceptions" => true }
+ get "/bad_params", env: { "action_dispatch.show_exceptions" => true }
assert_response 400
assert_equal "400 error fixture\n", body
- get "/not_found", headers: { "action_dispatch.show_exceptions" => true }
+ get "/not_found", env: { "action_dispatch.show_exceptions" => true }
assert_response 404
assert_equal "404 error fixture\n", body
- get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true }
+ get "/method_not_allowed", env: { "action_dispatch.show_exceptions" => true }
assert_response 405
assert_equal "", body
- get "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => true }
+ get "/unknown_http_method", env: { "action_dispatch.show_exceptions" => true }
assert_response 405
assert_equal "", body
+
+ get "/invalid_mimetype", headers: { "Accept" => "text/html,*", "action_dispatch.show_exceptions" => true }
+ assert_response 406
+ assert_equal "", body
end
test "localize rescue error page" do
@@ -70,11 +76,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
begin
@app = ProductionApp
- get "/", headers: { "action_dispatch.show_exceptions" => true }
+ get "/", env: { "action_dispatch.show_exceptions" => true }
assert_response 500
assert_equal "500 localized error fixture\n", body
- get "/not_found", headers: { "action_dispatch.show_exceptions" => true }
+ get "/not_found", env: { "action_dispatch.show_exceptions" => true }
assert_response 404
assert_equal "404 error fixture\n", body
ensure
@@ -85,14 +91,14 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
test "sets the HTTP charset parameter" do
@app = ProductionApp
- get "/", headers: { "action_dispatch.show_exceptions" => true }
+ get "/", env: { "action_dispatch.show_exceptions" => true }
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
test "show registered original exception for wrapped exceptions" do
@app = ProductionApp
- get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true }
+ get "/not_found_original_exception", env: { "action_dispatch.show_exceptions" => true }
assert_response 404
assert_match(/404 error/, body)
end
@@ -106,7 +112,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
@app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
- get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true }
+ get "/not_found_original_exception", env: { "action_dispatch.show_exceptions" => true }
assert_response 404
assert_equal "YOU FAILED", body
end
@@ -117,7 +123,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
@app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
- get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true }
+ get "/method_not_allowed", env: { "action_dispatch.show_exceptions" => true }
assert_response 405
assert_equal "", body
end
@@ -125,12 +131,12 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
test "bad params exception is returned in the correct format" do
@app = ProductionApp
- get "/bad_params", headers: { "action_dispatch.show_exceptions" => true }
+ get "/bad_params", env: { "action_dispatch.show_exceptions" => true }
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
assert_response 400
assert_match(/400 error/, body)
- get "/bad_params.json", headers: { "action_dispatch.show_exceptions" => true }
+ get "/bad_params.json", env: { "action_dispatch.show_exceptions" => true }
assert_equal "application/json; charset=utf-8", response.headers["Content-Type"]
assert_response 400
assert_equal("{\"status\":400,\"error\":\"Bad Request\"}", body)
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 a824ee0c84..d3b16d0328 100644
--- a/actionpack/test/dispatch/system_testing/driver_test.rb
+++ b/actionpack/test/dispatch/system_testing/driver_test.rb
@@ -2,6 +2,7 @@
require "abstract_unit"
require "action_dispatch/system_testing/driver"
+require "selenium/webdriver"
class DriverTest < ActiveSupport::TestCase
test "initializing the driver" do
@@ -22,6 +23,7 @@ class DriverTest < ActiveSupport::TestCase
driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :headless_chrome, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" })
assert_equal :selenium, driver.instance_variable_get(:@name)
assert_equal :headless_chrome, driver.instance_variable_get(:@browser).name
+ assert_instance_of Selenium::WebDriver::Chrome::Options, driver.instance_variable_get(:@browser).options
assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
end
@@ -30,6 +32,7 @@ class DriverTest < ActiveSupport::TestCase
driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :headless_firefox, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" })
assert_equal :selenium, driver.instance_variable_get(:@name)
assert_equal :headless_firefox, driver.instance_variable_get(:@browser).name
+ assert_instance_of Selenium::WebDriver::Firefox::Options, driver.instance_variable_get(:@browser).options
assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
end
@@ -51,4 +54,83 @@ class DriverTest < ActiveSupport::TestCase
test "registerable? returns false if driver is rack_test" do
assert_not ActionDispatch::SystemTesting::Driver.new(:rack_test).send(:registerable?)
end
+
+ test "define extra capabilities using chrome" do
+ driver_option = nil
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :chrome) do |option|
+ option.add_argument("start-maximized")
+ option.add_emulation(device_name: "iphone 6")
+ option.add_preference(:detach, true)
+
+ driver_option = option
+ end
+ driver.use
+
+ expected = { "goog:chromeOptions" => { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } }
+ assert_equal expected, driver_option.as_json
+ end
+
+ test "define extra capabilities using headless_chrome" do
+ driver_option = nil
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :headless_chrome) do |option|
+ option.add_argument("start-maximized")
+ option.add_emulation(device_name: "iphone 6")
+ option.add_preference(:detach, true)
+
+ driver_option = option
+ end
+ driver.use
+
+ expected = { "goog:chromeOptions" => { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } }
+ assert_equal expected, driver_option.as_json
+ end
+
+ test "define extra capabilities using firefox" do
+ driver_option = nil
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :firefox) do |option|
+ option.add_preference("browser.startup.homepage", "http://www.seleniumhq.com/")
+ option.add_argument("--host=127.0.0.1")
+
+ driver_option = option
+ end
+ driver.use
+
+ expected = { "moz:firefoxOptions" => { args: ["--host=127.0.0.1"], prefs: { "browser.startup.homepage" => "http://www.seleniumhq.com/" } } }
+ assert_equal expected, driver_option.as_json
+ end
+
+ test "define extra capabilities using headless_firefox" do
+ driver_option = nil
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :headless_firefox) do |option|
+ option.add_preference("browser.startup.homepage", "http://www.seleniumhq.com/")
+ option.add_argument("--host=127.0.0.1")
+
+ driver_option = option
+ end
+ driver.use
+
+ expected = { "moz:firefoxOptions" => { args: ["--host=127.0.0.1"], prefs: { "browser.startup.homepage" => "http://www.seleniumhq.com/" } } }
+ assert_equal expected, driver_option.as_json
+ end
+
+ test "does not define extra capabilities" do
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :firefox)
+
+ assert_nothing_raised do
+ 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/screenshot_helper_test.rb b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
index de79c05657..b0b36f9d74 100644
--- a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
+++ b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
@@ -3,6 +3,7 @@
require "abstract_unit"
require "action_dispatch/system_testing/test_helpers/screenshot_helper"
require "capybara/dsl"
+require "selenium/webdriver"
class ScreenshotHelperTest < ActiveSupport::TestCase
test "image path is saved in tmp directory" do
@@ -35,28 +36,34 @@ class ScreenshotHelperTest < ActiveSupport::TestCase
end
end
+ test "image name truncates names over 225 characters" do
+ new_test = DrivenBySeleniumWithChrome.new("x" * 400)
+
+ Rails.stub :root, Pathname.getwd do
+ assert_equal Rails.root.join("tmp/screenshots/#{"x" * 225}.png").to_s, new_test.send(:image_path)
+ end
+ end
+
test "defaults to simple output for the screenshot" do
new_test = DrivenBySeleniumWithChrome.new("x")
assert_equal "simple", new_test.send(:output_type)
end
test "display_image return artifact format when specify RAILS_SYSTEM_TESTING_SCREENSHOT environment" do
- begin
- original_output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"]
- ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = "artifact"
+ original_output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"]
+ ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = "artifact"
- new_test = DrivenBySeleniumWithChrome.new("x")
+ new_test = DrivenBySeleniumWithChrome.new("x")
- assert_equal "artifact", new_test.send(:output_type)
+ assert_equal "artifact", new_test.send(:output_type)
- Rails.stub :root, Pathname.getwd do
- new_test.stub :passed?, false do
- assert_match %r|url=artifact://.+?tmp/screenshots/failures_x\.png|, new_test.send(:display_image)
- end
+ Rails.stub :root, Pathname.getwd do
+ new_test.stub :passed?, false do
+ assert_match %r|url=artifact://.+?tmp/screenshots/failures_x\.png|, new_test.send(:display_image)
end
- ensure
- ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = original_output_type
end
+ ensure
+ ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = original_output_type
end
test "image path returns the absolute path from root" do
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 b078a5abc5..3319db1665 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "abstract_unit"
+require "selenium/webdriver"
class SetDriverToRackTestTest < DrivenByRackTest
test "uses rack_test" do
@@ -45,40 +46,3 @@ class SetHostTest < DrivenByRackTest
assert_equal "http://example.com", Capybara.app_host
end
end
-
-class UndefMethodsTest < DrivenBySeleniumWithChrome
- test "get" do
- exception = assert_raise NoMethodError do
- get "http://example.com"
- end
- assert_equal "System tests cannot make direct requests via #get; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
- end
-
- test "post" do
- exception = assert_raise NoMethodError do
- post "http://example.com"
- end
- assert_equal "System tests cannot make direct requests via #post; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
- end
-
- test "put" do
- exception = assert_raise NoMethodError do
- put "http://example.com"
- end
- assert_equal "System tests cannot make direct requests via #put; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
- end
-
- test "patch" do
- exception = assert_raise NoMethodError do
- patch "http://example.com"
- end
- assert_equal "System tests cannot make direct requests via #patch; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
- end
-
- test "delete" do
- exception = assert_raise NoMethodError do
- delete "http://example.com"
- end
- assert_equal "System tests cannot make direct requests via #delete; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
- end
-end
diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb
index f0b8f7785d..2629a61057 100644
--- a/actionpack/test/dispatch/test_response_test.rb
+++ b/actionpack/test/dispatch/test_response_test.rb
@@ -27,11 +27,4 @@ class TestResponseTest < ActiveSupport::TestCase
response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '{ "foo": "fighters" }')
assert_equal({ "foo" => "fighters" }, response.parsed_body)
end
-
- test "response status aliases deprecated" do
- response = ActionDispatch::TestResponse.create
- assert_deprecated { response.success? }
- assert_deprecated { response.missing? }
- assert_deprecated { response.error? }
- end
end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 21169fcb5c..03e5274541 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require "abstract_unit"
+require "tempfile"
+require "stringio"
module ActionDispatch
class UploadedFileTest < ActiveSupport::TestCase
@@ -11,109 +13,118 @@ module ActionDispatch
end
def test_original_filename
- uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new)
+ uf = Http::UploadedFile.new(filename: "foo", tempfile: Tempfile.new)
assert_equal "foo", uf.original_filename
end
def test_filename_is_different_object
file_str = "foo"
- uf = Http::UploadedFile.new(filename: file_str, tempfile: Object.new)
+ uf = Http::UploadedFile.new(filename: file_str, tempfile: Tempfile.new)
assert_not_equal file_str.object_id, uf.original_filename.object_id
end
def test_filename_should_be_in_utf_8
- uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new)
+ uf = Http::UploadedFile.new(filename: "foo", tempfile: Tempfile.new)
assert_equal "UTF-8", uf.original_filename.encoding.to_s
end
def test_filename_should_always_be_in_utf_8
uf = Http::UploadedFile.new(filename: "foo".encode(Encoding::SHIFT_JIS),
- tempfile: Object.new)
+ tempfile: Tempfile.new)
assert_equal "UTF-8", uf.original_filename.encoding.to_s
end
def test_content_type
- uf = Http::UploadedFile.new(type: "foo", tempfile: Object.new)
+ uf = Http::UploadedFile.new(type: "foo", tempfile: Tempfile.new)
assert_equal "foo", uf.content_type
end
def test_headers
- uf = Http::UploadedFile.new(head: "foo", tempfile: Object.new)
+ uf = Http::UploadedFile.new(head: "foo", tempfile: Tempfile.new)
assert_equal "foo", uf.headers
end
def test_tempfile
- uf = Http::UploadedFile.new(tempfile: "foo")
- assert_equal "foo", uf.tempfile
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal tf, uf.tempfile
end
- def test_to_io_returns_the_tempfile
- tf = Object.new
+ def test_to_io_returns_file
+ tf = Tempfile.new
uf = Http::UploadedFile.new(tempfile: tf)
- assert_equal tf, uf.to_io
+ assert_equal tf.to_io, uf.to_io
end
def test_delegates_path_to_tempfile
- tf = Class.new { def path; "thunderhorse" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "thunderhorse", uf.path
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal tf.path, uf.path
end
def test_delegates_open_to_tempfile
- tf = Class.new { def open; "thunderhorse" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "thunderhorse", uf.open
+ tf = Tempfile.new
+ tf.close
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal tf, uf.open
+ assert_not tf.closed?
end
def test_delegates_close_to_tempfile
- tf = Class.new { def close(unlink_now = false); "thunderhorse" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "thunderhorse", uf.close
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ uf.close
+ assert tf.closed?
end
def test_close_accepts_parameter
- tf = Class.new { def close(unlink_now = false); "thunderhorse: #{unlink_now}" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "thunderhorse: true", uf.close(true)
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ uf.close(true)
+ assert tf.closed?
+ assert_nil tf.path
end
def test_delegates_read_to_tempfile
- tf = Class.new { def read(length = nil, buffer = nil); "thunderhorse" end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
+ tf = Tempfile.new
+ tf << "thunderhorse"
+ tf.rewind
+ uf = Http::UploadedFile.new(tempfile: tf)
assert_equal "thunderhorse", uf.read
end
def test_delegates_read_to_tempfile_with_params
- tf = Class.new { def read(length = nil, buffer = nil); [length, buffer] end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal %w{ thunder horse }, uf.read(*%w{ thunder horse })
- end
-
- def test_delegate_respects_respond_to?
- tf = Class.new { def read; yield end; private :read }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_raises(NoMethodError) do
- uf.read
- end
+ tf = Tempfile.new
+ tf << "thunderhorse"
+ tf.rewind
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal "thunder", uf.read(7)
+ assert_equal "horse", uf.read(5, String.new)
end
def test_delegate_eof_to_tempfile
- tf = Class.new { def eof?; true end; }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_predicate uf, :eof?
+ tf = Tempfile.new
+ tf << "thunderhorse"
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal true, uf.eof?
+ tf.rewind
+ assert_equal false, uf.eof?
end
def test_delegate_to_path_to_tempfile
- tf = Class.new { def to_path; "/any/file/path" end; }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_equal "/any/file/path", uf.to_path
+ tf = Tempfile.new
+ uf = Http::UploadedFile.new(tempfile: tf)
+ assert_equal tf.to_path, uf.to_path
end
- def test_respond_to?
- tf = Class.new { def read; yield end }
- uf = Http::UploadedFile.new(tempfile: tf.new)
- assert_respond_to uf, :headers
- assert_respond_to uf, :read
+ def test_io_copy_stream
+ tf = Tempfile.new
+ tf << "thunderhorse"
+ tf.rewind
+ uf = Http::UploadedFile.new(tempfile: tf)
+ result = StringIO.new
+ IO.copy_stream(uf, result)
+ assert_equal "thunderhorse", result.string
end
end
end
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
index 3e7aea57f1..77c19369b0 100644
--- a/actionpack/test/journey/path/pattern_test.rb
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -34,17 +34,17 @@ module ActionDispatch
end
{
- "/:controller(/:action)" => %r{\A/(#{x})(?:/([^/.?]+))?},
- "/:controller/foo" => %r{\A/(#{x})/foo},
- "/:controller/:action" => %r{\A/(#{x})/([^/.?]+)},
- "/:controller" => %r{\A/(#{x})},
- "/:controller(/:action(/:id))" => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?},
- "/:controller/:action.xml" => %r{\A/(#{x})/([^/.?]+)\.xml},
- "/:controller.:format" => %r{\A/(#{x})\.([^/.?]+)},
- "/:controller(.:format)" => %r{\A/(#{x})(?:\.([^/.?]+))?},
- "/:controller/*foo" => %r{\A/(#{x})/(.+)},
- "/:controller/*foo/bar" => %r{\A/(#{x})/(.+)/bar},
- "/:foo|*bar" => %r{\A/(?:([^/.?]+)|(.+))},
+ "/:controller(/:action)" => %r{\A/(#{x})(?:/([^/.?]+))?(?:\b|\Z|/)},
+ "/:controller/foo" => %r{\A/(#{x})/foo(?:\b|\Z|/)},
+ "/:controller/:action" => %r{\A/(#{x})/([^/.?]+)(?:\b|\Z|/)},
+ "/:controller" => %r{\A/(#{x})(?:\b|\Z|/)},
+ "/:controller(/:action(/:id))" => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?(?:\b|\Z|/)},
+ "/:controller/:action.xml" => %r{\A/(#{x})/([^/.?]+)\.xml(?:\b|\Z|/)},
+ "/:controller.:format" => %r{\A/(#{x})\.([^/.?]+)(?:\b|\Z|/)},
+ "/:controller(.:format)" => %r{\A/(#{x})(?:\.([^/.?]+))?(?:\b|\Z|/)},
+ "/:controller/*foo" => %r{\A/(#{x})/(.+)(?:\b|\Z|/)},
+ "/:controller/*foo/bar" => %r{\A/(#{x})/(.+)/bar(?:\b|\Z|/)},
+ "/:foo|*bar" => %r{\A/(?:([^/.?]+)|(.+))(?:\b|\Z|/)},
}.each do |path, expected|
define_method(:"test_to_non_anchored_regexp_#{Regexp.escape(path)}") do
path = Pattern.build(
@@ -280,6 +280,15 @@ module ActionDispatch
assert_equal "list", match[1]
assert_equal "rss", match[2]
end
+
+ def test_named_captures
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
+
+ uri = "/books/list.rss"
+ match = path =~ uri
+ named_captures = { "action" => "list", "format" => "rss" }
+ assert_equal named_captures, match.named_captures
+ end
end
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 1f4e14aef6..fe0e3a975b 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -284,7 +284,7 @@ module ActionDispatch
def test_generate_missing_keys_no_matches_different_format_keys
get "/:controller/:action/:name", to: "foo#bar"
- primarty_parameters = {
+ primary_parameters = {
id: 1,
controller: "tasks",
action: "show",
@@ -297,9 +297,9 @@ module ActionDispatch
missing_parameters = {
missing_key => "task_1"
}
- request_parameters = primarty_parameters.merge(redirection_parameters).merge(missing_parameters)
+ request_parameters = primary_parameters.merge(redirection_parameters).merge(missing_parameters)
- message = "No route matches #{Hash[request_parameters.sort_by { |k, v|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}"
+ message = "No route matches #{Hash[request_parameters.sort_by { |k, _|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(
@@ -503,7 +503,6 @@ module ActionDispatch
end
private
-
def get(*args)
ActiveSupport::Deprecation.silence do
mapper.get(*args)