aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch')
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb26
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb231
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/filter_redirect.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb21
-rw-r--r--actionpack/lib/action_dispatch/http/rack_cache.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb47
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb6
-rw-r--r--actionpack/lib/action_dispatch/journey.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/builder.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/simulator.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/builder.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/dot.rb22
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/simulator.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser_extras.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb11
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb11
-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.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb241
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_locks.rb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/executor.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb38
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb77
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb13
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb3
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/endpoint.rb12
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb68
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb61
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb30
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb6
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb20
-rw-r--r--actionpack/lib/action_dispatch/system_testing/browser.rb49
-rw-r--r--actionpack/lib/action_dispatch/system_testing/driver.rb18
-rw-r--r--actionpack/lib/action_dispatch/system_testing/server.rb17
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb38
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb26
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb29
-rw-r--r--actionpack/lib/action_dispatch/testing/request_encoder.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb26
84 files changed, 1008 insertions, 374 deletions
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 985e0fb972..a8febc32b3 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Http
module Cache
@@ -95,7 +97,7 @@ module ActionDispatch
# support strong ETags and will ignore weak ETags entirely.
#
# Weak ETags are what we almost always need, so they're the default.
- # Check out `#strong_etag=` to provide a strong ETag validator.
+ # Check out #strong_etag= to provide a strong ETag validator.
def etag=(weak_validators)
self.weak_etag = weak_validators
end
@@ -131,7 +133,7 @@ module ActionDispatch
end
def generate_strong_etag(validators)
- %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
+ %("#{ActiveSupport::Digest.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
end
def cache_control_segments
@@ -164,19 +166,23 @@ module ActionDispatch
@cache_control = cache_control_headers
end
- def handle_conditional_get!
- if etag? || last_modified? || !@cache_control.empty?
- set_conditional_cache_control!(@cache_control)
- end
- end
-
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
NO_CACHE = "no-cache".freeze
PUBLIC = "public".freeze
PRIVATE = "private".freeze
MUST_REVALIDATE = "must-revalidate".freeze
- def set_conditional_cache_control!(cache_control)
+ def handle_conditional_get!
+ # Normally default cache control setting is handled by ETag
+ # middleware. But, if an etag is already set, the middleware
+ # defaults to `no-cache` unless a default `Cache-Control` value is
+ # previously set. So, set a default one here.
+ if (etag? || last_modified?) && !self._cache_control
+ self._cache_control = DEFAULT_CACHE_CONTROL
+ end
+ end
+
+ def merge_and_normalize_cache_control!(cache_control)
control = {}
cc_headers = cache_control_headers
if extras = cc_headers.delete(:extras)
@@ -189,7 +195,7 @@ module ActionDispatch
control.merge! cache_control
if control.empty?
- self._cache_control = DEFAULT_CACHE_CONTROL
+ # Let middleware handle default behavior
elsif control[:no_cache]
self._cache_control = NO_CACHE
if control[:extras]
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
new file mode 100644
index 0000000000..4883e23d24
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -0,0 +1,231 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/object/deep_dup"
+
+module ActionDispatch #:nodoc:
+ class ContentSecurityPolicy
+ class Middleware
+ CONTENT_TYPE = "Content-Type".freeze
+ POLICY = "Content-Security-Policy".freeze
+ POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
+
+ 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.content_security_policy
+ headers[header_name(request)] = policy.build(request.controller_instance)
+ end
+
+ response
+ end
+
+ private
+
+ def html_response?(headers)
+ if content_type = headers[CONTENT_TYPE]
+ content_type =~ /html/
+ end
+ end
+
+ def header_name(request)
+ if request.content_security_policy_report_only
+ POLICY_REPORT_ONLY
+ else
+ POLICY
+ end
+ end
+
+ def policy_present?(headers)
+ headers[POLICY] || headers[POLICY_REPORT_ONLY]
+ end
+ end
+
+ module Request
+ POLICY = "action_dispatch.content_security_policy".freeze
+ POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
+
+ def content_security_policy
+ get_header(POLICY)
+ end
+
+ def content_security_policy=(policy)
+ set_header(POLICY, policy)
+ end
+
+ def content_security_policy_report_only
+ get_header(POLICY_REPORT_ONLY)
+ end
+
+ def content_security_policy_report_only=(value)
+ set_header(POLICY_REPORT_ONLY, value)
+ end
+ end
+
+ MAPPINGS = {
+ self: "'self'",
+ unsafe_eval: "'unsafe-eval'",
+ unsafe_inline: "'unsafe-inline'",
+ none: "'none'",
+ http: "http:",
+ https: "https:",
+ data: "data:",
+ mediastream: "mediastream:",
+ blob: "blob:",
+ filesystem: "filesystem:",
+ report_sample: "'report-sample'",
+ strict_dynamic: "'strict-dynamic'"
+ }.freeze
+
+ DIRECTIVES = {
+ base_uri: "base-uri",
+ child_src: "child-src",
+ connect_src: "connect-src",
+ default_src: "default-src",
+ font_src: "font-src",
+ form_action: "form-action",
+ frame_ancestors: "frame-ancestors",
+ frame_src: "frame-src",
+ img_src: "img-src",
+ manifest_src: "manifest-src",
+ media_src: "media-src",
+ object_src: "object-src",
+ script_src: "script-src",
+ style_src: "style-src",
+ worker_src: "worker-src"
+ }.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 block_all_mixed_content(enabled = true)
+ if enabled
+ @directives["block-all-mixed-content"] = true
+ else
+ @directives.delete("block-all-mixed-content")
+ end
+ end
+
+ def plugin_types(*types)
+ if types.first
+ @directives["plugin-types"] = types
+ else
+ @directives.delete("plugin-types")
+ end
+ end
+
+ def report_uri(uri)
+ @directives["report-uri"] = [uri]
+ end
+
+ def require_sri_for(*types)
+ if types.first
+ @directives["require-sri-for"] = types
+ else
+ @directives.delete("require-sri-for")
+ end
+ end
+
+ def sandbox(*values)
+ if values.empty?
+ @directives["sandbox"] = true
+ elsif values.first
+ @directives["sandbox"] = values
+ else
+ @directives.delete("sandbox")
+ end
+ end
+
+ def upgrade_insecure_requests(enabled = true)
+ if enabled
+ @directives["upgrade-insecure-requests"] = true
+ else
+ @directives.delete("upgrade-insecure-requests")
+ 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 content security policy source: #{source.inspect}"
+ end
+ end
+ end
+
+ def apply_mapping(source)
+ MAPPINGS.fetch(source) do
+ raise ArgumentError, "Unknown content security 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 content security policy source: #{source.inspect}"
+ else
+ context.instance_exec(&source)
+ end
+ else
+ raise RuntimeError, "Unexpected content security 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 077ab2561f..ec86b8bc47 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/http/parameter_filter"
module ActionDispatch
@@ -7,7 +9,7 @@ module ActionDispatch
# sub-hashes of the params hash to filter. Filtering only certain sub-keys
# from a hash is possible by using the dot notation: 'credit_card.number'.
# If a block is given, each key and value of the params hash and all
- # sub-hashes is passed to it, the value or key can be replaced using
+ # sub-hashes is passed to it, where the value or the key can be replaced using
# String#replace or similar method.
#
# env["action_dispatch.parameter_filter"] = [:password]
@@ -46,7 +48,7 @@ module ActionDispatch
@filtered_env ||= env_filter.filter(@env)
end
- # Reconstructed a path with all sensitive GET parameters replaced.
+ # Reconstructs a path with all sensitive GET parameters replaced.
def filtered_path
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
end
diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb
index fc3c44582a..25394fe5dd 100644
--- a/actionpack/lib/action_dispatch/http/filter_redirect.rb
+++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Http
module FilterRedirect
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 3c03976f03..c3c2a9d8c5 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Http
# Provides access to the request's HTTP headers from the environment.
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 5994a01c78..d7435fa8df 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/attribute_accessors"
module ActionDispatch
@@ -10,9 +12,6 @@ module ActionDispatch
end
# The MIME type of the HTTP request, such as Mime[:xml].
- #
- # For backward compatibility, the post \format is extracted from the
- # X-Post-Data-Format HTTP header if present.
def content_mime_type
fetch_header("action_dispatch.request.content_type") do |k|
v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 74a3afab44..d2b2106845 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# -*- frozen-string-literal: true -*-
require "singleton"
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 8b04174f1f..342e6de312 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
# Build list of Mime types for HTTP responses
-# http://www.iana.org/assignments/media-types/
+# https://www.iana.org/assignments/media-types/
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
Mime::Type.register "text/plain", :text, [], %w(txt)
@@ -8,6 +10,7 @@ Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "text/vcard", :vcf
+Mime::Type.register "text/vtt", :vtt, %w(vtt)
Mime::Type.register "image/png", :png, [], %w(png)
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
@@ -18,6 +21,18 @@ Mime::Type.register "image/svg+xml", :svg
Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
+Mime::Type.register "audio/mpeg", :mp3, [], %w(mp1 mp2 mp3)
+Mime::Type.register "audio/ogg", :ogg, [], %w(oga ogg spx opus)
+Mime::Type.register "audio/aac", :m4a, %w( audio/mp4 ), %w(m4a mpg4 aac)
+
+Mime::Type.register "video/webm", :webm, [], %w(webm)
+Mime::Type.register "video/mp4", :mp4, [], %w(mp4 m4v)
+
+Mime::Type.register "font/otf", :otf, [], %w(otf)
+Mime::Type.register "font/ttf", :ttf, [], %w(ttf)
+Mime::Type.register "font/woff", :woff, [], %w(woff)
+Mime::Type.register "font/woff2", :woff2, [], %w(woff2)
+
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
Mime::Type.register "application/rss+xml", :rss
Mime::Type.register "application/atom+xml", :atom
@@ -26,7 +41,7 @@ Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ), %w(yml yaml)
Mime::Type.register "multipart/form-data", :multipart_form
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
-# http://www.ietf.org/rfc/rfc4627.txt
+# https://www.ietf.org/rfc/rfc4627.txt
# http://www.json.org/JSONRequest.html
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 1d2b4b902b..1d58964862 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/object/duplicable"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 7c585dbe68..8d7431fd6b 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Http
module Parameters
@@ -55,7 +57,7 @@ module ActionDispatch
query_parameters.dup
end
params.merge!(path_parameters)
- params = set_binary_encoding(params)
+ params = set_binary_encoding(params, params[:controller], params[:action])
set_header("action_dispatch.request.parameters", params)
params
end
@@ -64,6 +66,7 @@ module ActionDispatch
def path_parameters=(parameters) #:nodoc:
delete_header("action_dispatch.request.parameters")
+ parameters = set_binary_encoding(parameters, parameters[:controller], parameters[:action])
# If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
Request::Utils.check_param_encoding(parameters)
@@ -83,9 +86,10 @@ module ActionDispatch
private
- def set_binary_encoding(params)
- action = params[:action]
- if binary_params_for?(action)
+ def set_binary_encoding(params, controller, action)
+ return params unless controller && controller.valid_encoding?
+
+ if binary_params_for?(controller, action)
ActionDispatch::Request::Utils.each_param_value(params) do |param|
param.force_encoding ::Encoding::ASCII_8BIT
end
@@ -93,8 +97,8 @@ module ActionDispatch
params
end
- def binary_params_for?(action)
- controller_class.binary_params_for?(action)
+ def binary_params_for?(controller, action)
+ controller_class_for(controller).binary_params_for?(action)
rescue NameError
false
end
@@ -119,9 +123,4 @@ module ActionDispatch
end
end
end
-
- module ParamsParser
- include ActiveSupport::Deprecation::DeprecatedConstantAccessor
- deprecate_constant "ParseError", "ActionDispatch::Http::Parameters::ParseError"
- end
end
diff --git a/actionpack/lib/action_dispatch/http/rack_cache.rb b/actionpack/lib/action_dispatch/http/rack_cache.rb
index 003ae4029d..3e2d01aea3 100644
--- a/actionpack/lib/action_dispatch/http/rack_cache.rb
+++ b/actionpack/lib/action_dispatch/http/rack_cache.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rack/cache"
require "rack/cache/context"
require "active_support/cache"
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 6d42404a98..3838b84a7a 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "stringio"
require "active_support/inflector"
@@ -20,6 +22,7 @@ module ActionDispatch
include ActionDispatch::Http::Parameters
include ActionDispatch::Http::FilterParameters
include ActionDispatch::Http::URL
+ include ActionDispatch::ContentSecurityPolicy::Request
include Rack::Request::Env
autoload :Session, "action_dispatch/request/session"
@@ -74,10 +77,13 @@ module ActionDispatch
def controller_class
params = path_parameters
+ params[:action] ||= "index"
+ controller_class_for(params[:controller])
+ end
- if params.key?(:controller)
- controller_param = params[:controller].underscore
- params[:action] ||= "index"
+ def controller_class_for(name)
+ if name
+ controller_param = name.underscore
const_name = "#{controller_param.camelize}Controller"
ActiveSupport::Dependencies.constantize(const_name)
else
@@ -93,14 +99,14 @@ module ActionDispatch
end
# List of HTTP request methods from the following RFCs:
- # Hypertext Transfer Protocol -- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt)
- # HTTP Extensions for Distributed Authoring -- WEBDAV (http://www.ietf.org/rfc/rfc2518.txt)
- # Versioning Extensions to WebDAV (http://www.ietf.org/rfc/rfc3253.txt)
- # Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt)
- # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt)
- # Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt)
- # Calendar Extensions to WebDAV (http://www.ietf.org/rfc/rfc4791.txt)
- # PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt)
+ # Hypertext Transfer Protocol -- HTTP/1.1 (https://www.ietf.org/rfc/rfc2616.txt)
+ # HTTP Extensions for Distributed Authoring -- WEBDAV (https://www.ietf.org/rfc/rfc2518.txt)
+ # Versioning Extensions to WebDAV (https://www.ietf.org/rfc/rfc3253.txt)
+ # Ordered Collections Protocol (WebDAV) (https://www.ietf.org/rfc/rfc3648.txt)
+ # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (https://www.ietf.org/rfc/rfc3744.txt)
+ # Web Distributed Authoring and Versioning (WebDAV) SEARCH (https://www.ietf.org/rfc/rfc5323.txt)
+ # Calendar Extensions to WebDAV (https://www.ietf.org/rfc/rfc4791.txt)
+ # PATCH Method for HTTP (https://www.ietf.org/rfc/rfc5789.txt)
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
@@ -194,6 +200,23 @@ module ActionDispatch
@headers ||= Http::Headers.new(self)
end
+ # Early Hints is an HTTP/2 status code that indicates hints to help a client start
+ # making preparations for processing the final response.
+ #
+ # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers.
+ #
+ # The +send_early_hints+ method accepts a hash of links as follows:
+ #
+ # send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
+ #
+ # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the
+ # Early Hints headers are included by default if supported.
+ def send_early_hints(links)
+ return unless env["rack.early_hints"]
+
+ env["rack.early_hints"].call(links)
+ end
+
# Returns a +String+ with the last requested path including their params.
#
# # get '/foo'
@@ -298,7 +321,7 @@ module ActionDispatch
# variable is already set, wrap it in a StringIO.
def body
if raw_post = get_header("RAW_POST_DATA")
- raw_post.force_encoding(Encoding::BINARY)
+ raw_post = raw_post.dup.force_encoding(Encoding::BINARY)
StringIO.new(raw_post)
else
body_stream
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 3c91677d55..7e50cb6d23 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/attribute_accessors"
require "action_dispatch/http/filter_redirect"
require "action_dispatch/http/cache"
@@ -103,7 +105,7 @@ module ActionDispatch # :nodoc:
def body
@str_body ||= begin
- buf = ""
+ buf = "".dup
each { |chunk| buf << chunk }
buf
end
@@ -252,16 +254,15 @@ module ActionDispatch # :nodoc:
end
# Sets the HTTP character set. In case of +nil+ parameter
- # it sets the charset to utf-8.
+ # it sets the charset to +default_charset+.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
def charset=(charset)
- header_info = parsed_content_type_header
+ content_type = parsed_content_type_header.mime_type
if false == charset
- set_header CONTENT_TYPE, header_info.mime_type
+ set_content_type content_type, nil
else
- content_type = header_info.mime_type
set_content_type content_type, charset || self.class.default_charset
end
end
@@ -432,6 +433,7 @@ module ActionDispatch # :nodoc:
def before_committed
return if committed?
assign_default_content_type_and_charset!
+ merge_and_normalize_cache_control!(@cache_control)
handle_conditional_get!
handle_no_content!
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 225272d66e..0b162dc7f1 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Http
# Models uploaded files.
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index f902fe36e0..35ba44005a 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/attribute_accessors"
module ActionDispatch
@@ -155,7 +157,7 @@ module ActionDispatch
subdomain = options.fetch :subdomain, true
domain = options[:domain]
- host = ""
+ host = "".dup
if subdomain == true
return _host if domain.nil?
@@ -272,7 +274,7 @@ module ActionDispatch
def standard_port
case protocol
when "https://" then 443
- else 80
+ else 80
end
end
diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb
index d1cfc51f3e..2852efa6ae 100644
--- a/actionpack/lib/action_dispatch/journey.rb
+++ b/actionpack/lib/action_dispatch/journey.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey/router"
require "action_dispatch/journey/gtg/builder"
require "action_dispatch/journey/gtg/simulator"
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 326f4e52f9..0f04839d9b 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_controller/metal/exceptions"
module ActionDispatch
@@ -48,7 +50,7 @@ module ActionDispatch
unmatched_keys = (missing_keys || []) & constraints.keys
missing_keys = (missing_keys || []) - unmatched_keys
- message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
+ message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}".dup
message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
index 0f8bed89bf..44c31053cb 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey/gtg/transition_table"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
index 62f052ced6..2ee4f5c30c 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "strscan"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index 45aff287b1..ea647e051a 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey/nfa/dot"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
index 532f765094..d22302e101 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey/nfa/transition_table"
require "action_dispatch/journey/gtg/transition_table"
diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
index 8119e5d9da..56e9e3c83d 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Journey # :nodoc:
module NFA # :nodoc:
@@ -7,16 +9,16 @@ module ActionDispatch
" #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
}
- #memo_nodes = memos.values.flatten.map { |n|
- # label = n
- # if Journey::Route === n
- # label = "#{n.verb.source} #{n.path.spec}"
- # end
- # " #{n.object_id} [label=\"#{label}\", shape=box];"
- #}
- #memo_edges = memos.flat_map { |k, memos|
- # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
- #}.uniq
+ # memo_nodes = memos.values.flatten.map { |n|
+ # label = n
+ # if Journey::Route === n
+ # label = "#{n.verb.source} #{n.path.spec}"
+ # end
+ # " #{n.object_id} [label=\"#{label}\", shape=box];"
+ # }
+ # memo_edges = memos.flat_map { |k, memos|
+ # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
+ # }.uniq
<<-eodot
digraph nfa {
diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
index 324d0eed15..8efe48d91c 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "strscan"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
index 543a670da0..fe55861507 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey/nfa/dot"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index 0d874a84c9..08b931a3cd 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey/visitors"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb
index 4c7e82d93c..18ec6c9b9b 100644
--- a/actionpack/lib/action_dispatch/journey/parser_extras.rb
+++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey/scanner"
require "action_dispatch/journey/nodes/node"
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index cf0108ec32..2d85a89a56 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Journey # :nodoc:
module Path # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 0acbac1d9d..8165709a3d 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
# :stopdoc:
module Journey
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index d55e1399e4..30af3ff930 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey/router/utils"
require "action_dispatch/journey/routes"
require "action_dispatch/journey/formatter"
@@ -41,6 +43,10 @@ module ActionDispatch
req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
end
+ parameters = route.defaults.merge parameters.transform_values { |val|
+ val.dup.force_encoding(::Encoding::UTF_8)
+ }
+
req.path_parameters = set_params.merge parameters
status, headers, body = route.app.serve(req)
@@ -55,7 +61,7 @@ module ActionDispatch
return [status, headers, body]
end
- return [404, { "X-Cascade" => "pass" }, ["Not Found"]]
+ [404, { "X-Cascade" => "pass" }, ["Not Found"]]
end
def recognize(rails_req)
@@ -65,6 +71,7 @@ module ActionDispatch
rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
end
+ parameters = route.defaults.merge parameters
yield(route, parameters)
end
end
@@ -117,7 +124,7 @@ module ActionDispatch
routes.map! { |r|
match_data = r.path.match(req.path_info)
- path_parameters = r.defaults.dup
+ path_parameters = {}
match_data.names.zip(match_data.captures) { |name, val|
path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
}
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index 6d400f3364..df3f79a407 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
@@ -13,23 +15,24 @@ module ActionDispatch
# normalize_path("") # => "/"
# normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
+ path ||= ""
encoding = path.encoding
- path = "/#{path}"
+ path = "/#{path}".dup
path.squeeze!("/".freeze)
path.sub!(%r{/+\Z}, "".freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
- path = "/" if path == "".freeze
+ path = "/".dup if path == "".freeze
path.force_encoding(encoding)
path
end
# URI path and fragment escaping
- # http://tools.ietf.org/html/rfc3986
+ # https://tools.ietf.org/html/rfc3986
class UriEncoder # :nodoc:
ENCODE = "%%%02X".freeze
US_ASCII = Encoding::US_ASCII
UTF_8 = Encoding::UTF_8
- EMPTY = "".force_encoding(US_ASCII).freeze
+ EMPTY = "".dup.force_encoding(US_ASCII).freeze
DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
ALPHA = "a-zA-Z".freeze
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
index f7b009109e..639c063495 100644
--- a/actionpack/lib/action_dispatch/journey/routes.rb
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Journey # :nodoc:
# The Routing table. Contains all routes for a system. Routes can be
diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb
index 7dbb39b26d..4ae77903fa 100644
--- a/actionpack/lib/action_dispatch/journey/scanner.rb
+++ b/actionpack/lib/action_dispatch/journey/scanner.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require "strscan"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 335797f4b9..3395471a85 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
# :stopdoc:
module Journey
@@ -175,7 +177,7 @@ module ActionDispatch
last_child = node.children.last
node.children.inject(seed) { |s, c|
string = visit(c, s)
- string << "|".freeze unless last_child == c
+ string << "|" unless last_child == c
string
}
end
@@ -185,7 +187,7 @@ module ActionDispatch
end
def visit_GROUP(node, seed)
- visit(node.left, seed << "(".freeze) << ")".freeze
+ visit(node.left, seed.dup << "(") << ")"
end
INSTANCE = new
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index ff129cf96a..5b2ad36dd5 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
# Provides callbacks to be executed before and after dispatching the request.
class Callbacks
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 533925ebe1..c45d947904 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/keys"
require "active_support/key_generator"
require "active_support/message_verifier"
@@ -47,6 +49,18 @@ module ActionDispatch
get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT
end
+ def use_authenticated_cookie_encryption
+ get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION
+ end
+
+ def encrypted_cookie_cipher
+ get_header Cookies::ENCRYPTED_COOKIE_CIPHER
+ end
+
+ def signed_cookie_digest
+ get_header Cookies::SIGNED_COOKIE_DIGEST
+ end
+
def secret_token
get_header Cookies::SECRET_TOKEN
end
@@ -62,6 +76,11 @@ module ActionDispatch
def cookies_digest
get_header Cookies::COOKIES_DIGEST
end
+
+ def cookies_rotations
+ get_header Cookies::COOKIES_ROTATIONS
+ end
+
# :startdoc:
end
@@ -81,16 +100,17 @@ module ActionDispatch
# cookies[:lat_lon] = JSON.generate([47.68, -122.37])
#
# # Sets a cookie that expires in 1 hour.
- # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour }
+ #
+ # # Sets a cookie that expires at a specific time.
+ # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's `secrets.secret_key_base` value.
# # It can be read using the signed method `cookies.signed[:name]`
# cookies.signed[:user_id] = current_user.id
#
# # Sets an encrypted cookie value before sending it to the client which
# # prevent users from reading and tampering with its value.
- # # The cookie is signed by your app's `secrets.secret_key_base` value.
# # It can be read using the encrypted method `cookies.encrypted[:name]`
# cookies.encrypted[:discount] = 45
#
@@ -98,7 +118,7 @@ module ActionDispatch
# cookies.permanent[:login] = "XJ-122"
#
# # You can also chain these methods:
- # cookies.permanent.signed[:login] = "XJ-122"
+ # cookies.signed.permanent[:login] = "XJ-122"
#
# Examples of reading:
#
@@ -116,7 +136,7 @@ module ActionDispatch
#
# cookies[:name] = {
# value: 'a yummy cookie',
- # expires: 1.year.from_now,
+ # expires: 1.year,
# domain: 'domain.com'
# }
#
@@ -141,8 +161,8 @@ module ActionDispatch
#
# * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
# set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
- # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 1.
- # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
+ # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 2.
+ # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object.
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
@@ -154,10 +174,14 @@ module ActionDispatch
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze
+ USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption".freeze
+ ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher".freeze
+ SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
+ COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -186,10 +210,10 @@ module ActionDispatch
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
# cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
+ # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
#
# Example:
#
@@ -198,40 +222,28 @@ module ActionDispatch
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||=
- if upgrade_legacy_signed_cookies?
- UpgradeLegacySignedCookieJar.new(self)
- else
- SignedCookieJar.new(self)
- end
+ @signed ||= SignedKeyRotatingCookieJar.new(self)
end
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
+ # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
# If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
# are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
#
# Example:
#
# cookies.encrypted[:discount] = 45
- # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
#
# cookies.encrypted[:discount] # => 45
def encrypted
- @encrypted ||=
- if upgrade_legacy_signed_cookies?
- UpgradeLegacyEncryptedCookieJar.new(self)
- elsif upgrade_legacy_hmac_aes_cbc_cookies?
- UpgradeLegacyHmacAesCbcCookieJar.new(self)
- else
- EncryptedCookieJar.new(self)
- end
+ @encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
end
# Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
@@ -252,34 +264,18 @@ module ActionDispatch
end
def upgrade_legacy_hmac_aes_cbc_cookies?
- request.secret_key_base.present? &&
- request.authenticated_encrypted_cookie_salt.present? &&
- request.encrypted_signed_cookie_salt.present? &&
- request.encrypted_cookie_salt.present?
+ request.secret_key_base.present? &&
+ request.encrypted_signed_cookie_salt.present? &&
+ request.encrypted_cookie_salt.present? &&
+ request.use_authenticated_cookie_encryption
end
- end
- # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
- # to the Message{Encryptor,Verifier} allows us to handle the
- # (de)serialization step within the cookie jar, which gives us the
- # opportunity to detect and migrate legacy cookies.
- module VerifyAndUpgradeLegacySignedMessage # :nodoc:
- def initialize(*args)
- super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
- end
-
- def verify_and_upgrade_legacy_signed_message(name, signed_message)
- deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value|
- self[name] = { value: value }
+ def encrypted_cookie_cipher
+ request.encrypted_cookie_cipher || "aes-256-gcm"
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
- end
- private
- def parse(name, signed_message)
- super || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ def signed_cookie_digest
+ request.signed_cookie_digest || "SHA1"
end
end
@@ -342,6 +338,9 @@ module ActionDispatch
end
alias :has_key? :key?
+ # Returns the cookies as Hash.
+ alias :to_hash :to_h
+
def update(other_hash)
@cookies.update other_hash.stringify_keys
self
@@ -358,7 +357,11 @@ module ActionDispatch
@cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
end
- def handle_options(options) #:nodoc:
+ def handle_options(options) # :nodoc:
+ if options[:expires].respond_to?(:from_now)
+ options[:expires] = options[:expires].from_now
+ end
+
options[:path] ||= "/"
if options[:domain] == :all || options[:domain] == "all"
@@ -486,6 +489,14 @@ module ActionDispatch
def request; @parent_jar.request; end
private
+ def expiry_options(options)
+ if options[:expires].respond_to?(:from_now)
+ { expires_in: options[:expires] }
+ else
+ { expires_at: options[:expires] }
+ end
+ end
+
def parse(name, data); data; end
def commit(options); end
end
@@ -509,6 +520,7 @@ module ActionDispatch
module SerializedCookieJars # :nodoc:
MARSHAL_SIGNATURE = "\x04\x08".freeze
+ SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
protected
def needs_migration?(value)
@@ -519,12 +531,16 @@ module ActionDispatch
serializer.dump(value)
end
- def deserialize(name, value)
+ def deserialize(name)
+ rotate = false
+ value = yield -> { rotate = true }
+
if value
- if needs_migration?(value)
- Marshal.load(value).tap do |v|
- self[name] = { value: v }
- end
+ case
+ when needs_migration?(value)
+ self[name] = Marshal.load(value)
+ when rotate
+ self[name] = serializer.load(value)
else
serializer.load(value)
end
@@ -546,104 +562,97 @@ module ActionDispatch
def digest
request.cookies_digest || "SHA1"
end
-
- def key_generator
- request.key_generator
- end
end
- class SignedCookieJar < AbstractCookieJar # :nodoc:
+ class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
include SerializedCookieJars
def initialize(parent_jar)
super
- secret = key_generator.generate_key(request.signed_cookie_salt)
- @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+
+ secret = request.key_generator.generate_key(request.signed_cookie_salt)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
+
+ request.cookies_rotations.signed.each do |*secrets, **options|
+ @verifier.rotate(*secrets, serializer: SERIALIZER, **options)
+ end
+
+ if upgrade_legacy_signed_cookies?
+ @verifier.rotate request.secret_token, serializer: SERIALIZER
+ end
end
private
def parse(name, signed_message)
- deserialize name, @verifier.verified(signed_message)
+ deserialize(name) do |rotate|
+ @verifier.verified(signed_message, on_rotation: rotate)
+ end
end
def commit(options)
- options[:value] = @verifier.generate(serialize(options[:value]))
+ options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options))
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
end
- # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
- # secrets.secret_token and secrets.secret_key_base are both set. It reads
- # legacy cookies signed with the old dummy key generator and signs and
- # re-saves them using the new key generator to provide a smooth upgrade path.
- class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
- include VerifyAndUpgradeLegacySignedMessage
- end
-
- class EncryptedCookieJar < AbstractCookieJar # :nodoc:
+ class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
include SerializedCookieJars
def initialize(parent_jar)
super
- if ActiveSupport::LegacyKeyGenerator === key_generator
- raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " \
- "Read the upgrade documentation to learn more about this new config option."
+ if request.use_authenticated_cookie_encryption
+ key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER)
+ else
+ key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len)
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
+ end
+
+ request.cookies_rotations.encrypted.each do |*secrets, **options|
+ @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
end
- cipher = "aes-256-gcm"
- key_len = ActiveSupport::MessageEncryptor.key_len(cipher)
- secret = key_generator.generate_key(request.authenticated_encrypted_cookie_salt || "")[0, key_len]
+ if upgrade_legacy_hmac_aes_cbc_cookies?
+ legacy_cipher = "aes-256-cbc"
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher))
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
+ end
+
+ if upgrade_legacy_signed_cookies?
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
+ end
end
private
def parse(name, encrypted_message)
- deserialize name, @encryptor.decrypt_and_verify(encrypted_message)
- rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
- nil
+ deserialize(name) do |rotate|
+ @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
+ end
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
+ parse_legacy_signed_message(name, encrypted_message)
end
def commit(options)
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]))
+ options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options))
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
- end
-
- # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
- # instead of EncryptedCookieJar if secrets.secret_token and secrets.secret_key_base
- # are both set. It reads legacy cookies signed with the old dummy key generator and
- # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
- class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
- include VerifyAndUpgradeLegacySignedMessage
- end
-
- # UpgradeLegacyHmacAesCbcCookieJar is used by ActionDispatch::Session::CookieStore
- # to upgrade cookies encrypted with AES-256-CBC with HMAC to AES-256-GCM
- class UpgradeLegacyHmacAesCbcCookieJar < EncryptedCookieJar
- def initialize(parent_jar)
- super
- secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
- sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
-
- @legacy_encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
- end
+ def parse_legacy_signed_message(name, legacy_signed_message)
+ if defined?(@legacy_verifier)
+ deserialize(name) do |rotate|
+ rotate.call
- def decrypt_and_verify_legacy_encrypted_message(name, signed_message)
- deserialize(name, @legacy_encryptor.decrypt_and_verify(signed_message)).tap do |value|
- self[name] = { value: value }
- end
- rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
- nil
- end
-
- private
- def parse(name, signed_message)
- super || decrypt_and_verify_legacy_encrypted_message(name, signed_message)
+ @legacy_verifier.verified(legacy_signed_message)
+ end
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 336a775880..511306eb0e 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/http/request"
require "action_dispatch/middleware/exception_wrapper"
require "action_dispatch/routing/inspector"
@@ -21,7 +23,7 @@ module ActionDispatch
if clean_params.empty?
"None"
else
- PP.pp(clean_params, "", 200)
+ PP.pp(clean_params, "".dup, 200)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb
index 74b952528e..03760438f7 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_locks.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
# This middleware can be used to diagnose deadlocks in the autoload interlock.
#
@@ -41,7 +43,7 @@ module ActionDispatch
private
def render_details(req)
- threads = ActiveSupport::Dependencies.interlock.raw_state do |threads|
+ threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads|
# The Interlock itself comes to a complete halt as long as this block
# is executing. That gives us a more consistent picture of everything,
# but creates a pretty strong Observer Effect.
@@ -51,29 +53,29 @@ module ActionDispatch
# strictly diagnostic tool (to be used when something has gone wrong),
# and not for any sort of general monitoring.
- threads.each.with_index do |(thread, info), idx|
+ raw_threads.each.with_index do |(thread, info), idx|
info[:index] = idx
info[:backtrace] = thread.backtrace
end
- threads
+ raw_threads
end
str = threads.map do |thread, info|
if info[:exclusive]
- lock_state = "Exclusive"
+ lock_state = "Exclusive".dup
elsif info[:sharing] > 0
- lock_state = "Sharing"
+ lock_state = "Sharing".dup
lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
else
- lock_state = "No lock"
+ lock_state = "No lock".dup
end
if info[:waiting]
lock_state << " (yielded share)"
end
- msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
+ msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup
if info[:sleeper]
msg << " Waiting in #{info[:sleeper]}"
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 08b4541d24..d1b4508378 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/module/attribute_accessors"
require "rack/utils"
@@ -23,6 +25,7 @@ module ActionDispatch
"ActionView::MissingTemplate" => "missing_template",
"ActionController::RoutingError" => "routing_error",
"AbstractController::ActionNotFound" => "unknown_action",
+ "ActiveRecord::StatementInvalid" => "invalid_statement",
"ActionView::Template::Error" => "template_error"
)
diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb
index 3d43f97a2b..129b18d3d9 100644
--- a/actionpack/lib/action_dispatch/middleware/executor.rb
+++ b/actionpack/lib/action_dispatch/middleware/executor.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rack/body_proxy"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 6b29ce63ba..3e11846778 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/keys"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 46f0f675b9..3feb3a19f3 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -1,11 +1,13 @@
+# frozen_string_literal: true
+
module ActionDispatch
# When called, this middleware renders an error page. By default if an HTML
- # response is expected it will render static error pages from the `/public`
+ # response is expected it will render static error pages from the <tt>/public</tt>
# directory. For example when this middleware receives a 500 response it will
- # render the template found in `/public/500.html`.
+ # render the template found in <tt>/public/500.html</tt>.
# If an internationalized locale is set, this middleware will attempt to render
- # the template in `/public/500.<locale>.html`. If an internationalized template
- # is not found it will fall back on `/public/500.html`.
+ # the template in <tt>/public/500.<locale>.html</tt>. If an internationalized template
+ # is not found it will fall back on <tt>/public/500.html</tt>.
#
# When a request with a content type other than HTML is made, this middleware
# will attempt to convert error information into the appropriate response type.
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 6d64b1424b..8bb3ba7504 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
# ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
# callbacks, intended to assist with code reloading during development.
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 53d5a4918c..35158f9062 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "ipaddr"
module ActionDispatch
@@ -10,7 +12,7 @@ module ActionDispatch
# by @gingerlime. A more detailed explanation of the algorithm is given
# at GetIp#calculate_ip.
#
- # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
# requires. Some Rack servers simply drop preceding headers, and only report
# the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
# If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
@@ -29,7 +31,7 @@ module ActionDispatch
# The default trusted IPs list simply includes IP addresses that are
# guaranteed by the IP specification to be private addresses. Those will
# not be the ultimate client IP in production, and so are discarded. See
- # http://en.wikipedia.org/wiki/Private_network for details.
+ # https://en.wikipedia.org/wiki/Private_network for details.
TRUSTED_PROXIES = [
"127.0.0.1", # localhost IPv4
"::1", # localhost IPv6
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 1925ffd9dd..da2871b551 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "securerandom"
require "active_support/core_ext/string/access"
@@ -28,7 +30,7 @@ module ActionDispatch
private
def make_request_id(request_id)
if request_id.presence
- request_id.gsub(/[^\w\-]/, "".freeze).first(255)
+ request_id.gsub(/[^\w\-@]/, "".freeze).first(255)
else
internal_request_id
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 21ccf5a097..5b0be96223 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rack/utils"
require "rack/request"
require "rack/session/abstract/id"
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index 71274bc13a..a6d965a644 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/middleware/session/abstract_store"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 57d325a9d8..4ea96196d3 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/keys"
require "action_dispatch/middleware/session/abstract_store"
require "rack/session/cookie"
@@ -19,39 +21,25 @@ module ActionDispatch
# knowing your app's secret key, but can easily read their +user_id+. This
# was the default for Rails 3 apps.
#
- # If you have secret_key_base set, your cookies will be encrypted. This
+ # Your cookies will be encrypted using your apps secret_key_base. This
# goes a step further than signed cookies in that encrypted cookies cannot
# be altered or read by users. This is the default starting in Rails 4.
#
- # If you have both secret_token and secret_key_base set, your cookies will
- # be encrypted, and signed cookies generated by Rails 3 will be
- # transparently read and encrypted to provide a smooth upgrade path.
- #
- # Configure your session store in config/initializers/session_store.rb:
+ # Configure your session store in <tt>config/initializers/session_store.rb</tt>:
#
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
#
- # Configure your secret key in config/secrets.yml:
- #
- # development:
- # secret_key_base: 'secret key'
- #
- # To generate a secret key for an existing application, run `rails secret`.
+ # By default, your secret key base is derived from your application name in
+ # the test and development environments. In all other environments, it is stored
+ # encrypted in the <tt>config/credentials.yml.enc</tt> file.
#
- # If you are upgrading an existing Rails 3 app, you should leave your
- # existing secret_token in place and simply add the new secret_key_base.
- # Note that you should wait to set secret_key_base until you have 100% of
- # your userbase on Rails 4 and are reasonably sure you will not need to
- # rollback to Rails 3. This is because cookies signed based on the new
- # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
- # You are free to leave your existing secret_token in place, not set the
- # new secret_key_base, and ignore the deprecation warnings until you are
- # reasonably sure that your upgrade is otherwise complete. Additionally,
- # you should take care to make sure you are not relying on the ability to
- # decode signed cookies generated by your app in external applications or
- # JavaScript before upgrading.
+ # If your application was not updated to Rails 5.2 defaults, the secret_key_base
+ # will be found in the old <tt>config/secrets.yml</tt> file.
#
- # Note that changing the secret key will invalidate all existing sessions!
+ # Note that changing your secret_key_base will invalidate all existing session.
+ # Additionally, you should take care to make sure you are not relying on the
+ # ability to decode signed cookies generated by your app in external
+ # applications or JavaScript before changing it.
#
# Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
# options described there can be used to customize the session cookie that
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index ee2b1f26ad..914df3a2b1 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/middleware/session/abstract_store"
begin
require "rack/session/dalli"
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 5a99714ec2..3c88afd4d3 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/http/request"
require "action_dispatch/middleware/exception_wrapper"
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 557721c301..ef633aadc6 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -1,45 +1,52 @@
+# frozen_string_literal: true
+
module ActionDispatch
- # This middleware is added to the stack when `config.force_ssl = true`, and is passed
- # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
+ # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
+ # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
# requests:
#
- # 1. TLS redirect: Permanently redirects http:// requests to https://
- # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
- # to modify the destination URL
- # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
- # `redirect: false` to disable this feature.
+ # 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+
+ # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+
+ # to modify the destination URL
+ # (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set
+ # <tt>redirect: false</tt> to disable this feature.
+ #
+ # Requests can opt-out of redirection with +exclude+:
+ #
+ # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
#
- # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
- # mustn't be sent along with http:// requests. Enabled by default. Set
- # `config.ssl_options` with `secure_cookies: false` to disable this feature.
+ # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they
+ # must not be sent along with +http://+ requests. Enabled by default. Set
+ # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature.
#
- # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
- # this site as TLS-only and automatically redirect non-TLS requests.
- # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
+ # 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember
+ # this site as TLS-only and automatically redirect non-TLS requests.
+ # Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable.
#
- # Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
- # * `expires`: How long, in seconds, these settings will stick. The minimum
- # required to qualify for browser preload lists is `18.weeks`. Defaults to
- # `180.days` (recommended).
- # * `subdomains`: Set to `true` to tell the browser to apply these settings
- # to all subdomains. This protects your cookies from interception by a
- # vulnerable site on a subdomain. Defaults to `true`.
- # * `preload`: Advertise that this site may be included in browsers'
- # preloaded HSTS lists. HSTS protects your site on every visit *except the
- # first visit* since it hasn't seen your HSTS header yet. To close this
- # gap, browser vendors include a baked-in list of HSTS-enabled sites.
- # Go to https://hstspreload.appspot.com to submit your site for inclusion.
- # Defaults to `false`.
+ # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
#
- # To turn off HSTS, omitting the header is not enough. Browsers will remember the
- # original HSTS directive until it expires. Instead, use the header to tell browsers to
- # expire HSTS immediately. Setting `hsts: false` is a shortcut for
- # `hsts: { expires: 0 }`.
+ # * +expires+: How long, in seconds, these settings will stick. The minimum
+ # required to qualify for browser preload lists is 18 weeks. Defaults to
+ # 180 days (recommended).
#
- # Requests can opt-out of redirection with `exclude`:
+ # * +subdomains+: Set to +true+ to tell the browser to apply these settings
+ # to all subdomains. This protects your cookies from interception by a
+ # vulnerable site on a subdomain. Defaults to +true+.
#
- # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
+ # * +preload+: Advertise that this site may be included in browsers'
+ # preloaded HSTS lists. HSTS protects your site on every visit <i>except the
+ # first visit</i> since it hasn't seen your HSTS header yet. To close this
+ # gap, browser vendors include a baked-in list of HSTS-enabled sites.
+ # Go to https://hstspreload.org to submit your site for inclusion.
+ # Defaults to +false+.
+ #
+ # To turn off HSTS, omitting the header is not enough. Browsers will remember the
+ # original HSTS directive until it expires. Instead, use the header to tell browsers to
+ # expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for
+ # <tt>hsts: { expires: 0 }</tt>.
class SSL
+ # :stopdoc:
+
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
HSTS_EXPIRES_IN = 15552000
@@ -92,9 +99,9 @@ module ActionDispatch
end
end
- # http://tools.ietf.org/html/rfc6797#section-6.1
+ # https://tools.ietf.org/html/rfc6797#section-6.1
def build_hsts_header(hsts)
- value = "max-age=#{hsts[:expires].to_i}"
+ value = "max-age=#{hsts[:expires].to_i}".dup
value << "; includeSubDomains" if hsts[:subdomains]
value << "; preload" if hsts[:preload]
value
@@ -133,7 +140,7 @@ module ActionDispatch
host = @redirect[:host] || request.host
port = @redirect[:port] || request.port
- location = "https://#{host}"
+ location = "https://#{host}".dup
location << ":#{port}" if port != 80 && port != 443
location << request.fullpath
location
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 6949b31e75..b82f8aa3a3 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflector/methods"
require "active_support/dependencies"
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index fb99f13a1c..23492e14eb 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rack/utils"
require "active_support/core_ext/uri"
@@ -33,7 +35,7 @@ module ActionDispatch
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
- path = File.join(@root, p.force_encoding(Encoding::UTF_8))
+ path = File.join(@root, p.dup.force_encoding(Encoding::UTF_8))
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
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
new file mode 100644
index 0000000000..e1b129ccc5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
@@ -0,0 +1,21 @@
+<header>
+ <h1>
+ <%= @exception.class.to_s %>
+ <% if @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 %>
+ <% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
+ <br />To resolve this issue run: bin/rails active_storage:install
+ <% end %>
+ </h2>
+
+ <%= render template: "rescues/_source" %>
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
+</div>
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
new file mode 100644
index 0000000000..033518cf8a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb
@@ -0,0 +1,13 @@
+<%= @exception.class.to_s %><%
+ if @request.parameters['controller']
+%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+<% end %>
+
+<%= @exception.message %>
+<% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
+To resolve this issue run: bin/rails active_storage:install
+<% end %>
+
+<%= render template: "rescues/_source" %>
+<%= render template: "rescues/_trace" %>
+<%= render template: "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index e0509f56f4..39ea25bdfc 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -106,6 +106,7 @@
.line {
padding-left: 10px;
+ white-space: pre;
}
.line:hover {
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 2d21ae63f5..1fa0691303 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -17,6 +17,10 @@
line-height: 15px;
}
+ #route_table thead tr.bottom th input#search {
+ -webkit-appearance: textfield;
+ }
+
#route_table tbody tr {
border-bottom: 1px solid #ddd;
}
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 7662e164b8..eb6fbca6ba 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require "action_dispatch"
+require "active_support/messages/rotation_configuration"
module ActionDispatch
class Railtie < Rails::Railtie # :nodoc:
@@ -16,15 +19,21 @@ module ActionDispatch
config.action_dispatch.signed_cookie_salt = "signed cookie"
config.action_dispatch.encrypted_cookie_salt = "encrypted cookie"
config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"
+ config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie"
config.action_dispatch.use_authenticated_cookie_encryption = false
config.action_dispatch.perform_deep_munge = true
config.action_dispatch.default_headers = {
"X-Frame-Options" => "SAMEORIGIN",
"X-XSS-Protection" => "1; mode=block",
- "X-Content-Type-Options" => "nosniff"
+ "X-Content-Type-Options" => "nosniff",
+ "X-Download-Options" => "noopen",
+ "X-Permitted-Cross-Domain-Policies" => "none",
+ "Referrer-Policy" => "strict-origin-when-cross-origin"
}
+ config.action_dispatch.cookies_rotations = ActiveSupport::Messages::RotationConfiguration.new
+
config.eager_load_namespaces << ActionDispatch
initializer "action_dispatch.configure" do |app|
@@ -37,8 +46,6 @@ module ActionDispatch
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
- config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" if config.action_dispatch.use_authenticated_cookie_encryption
-
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 3547a8604f..000847e193 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rack/session/abstract/id"
module ActionDispatch
@@ -128,6 +130,7 @@ module ActionDispatch
load_for_read!
@delegate.dup.delete_if { |_, v| v.nil? }
end
+ alias :to_h :to_hash
# Updates the session with given Hash.
#
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 4f79c4c21e..0ae464082d 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
class Request
class Utils # :nodoc:
@@ -32,7 +34,7 @@ module ActionDispatch
unless params.valid_encoding?
# Raise Rack::Utils::InvalidParameterError for consistency with Rack.
# ActionDispatch::Request#GET will re-raise as a BadRequest error.
- raise Rack::Utils::InvalidParameterError, "Non UTF-8 value: #{params}"
+ raise Rack::Utils::InvalidParameterError, "Invalid encoding for parameter: #{params.scrub}"
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 87dd1eba38..72f7407c6e 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/string/filters"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/routing/endpoint.rb b/actionpack/lib/action_dispatch/routing/endpoint.rb
index 88aa13c3e8..24dced1efd 100644
--- a/actionpack/lib/action_dispatch/routing/endpoint.rb
+++ b/actionpack/lib/action_dispatch/routing/endpoint.rb
@@ -1,10 +1,14 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Routing
class Endpoint # :nodoc:
- def dispatcher?; false; end
- def redirect?; false; end
- def matches?(req); true; end
- def app; self; end
+ def dispatcher?; false; end
+ def redirect?; false; end
+ def engine?; rack_app.respond_to?(:routes); end
+ def matches?(req); true; end
+ def app; self; end
+ def rack_app; app; end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index 9aa4b92df2..a2205569b4 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "delegate"
require "active_support/core_ext/string/strip"
@@ -13,7 +15,7 @@ module ActionDispatch
end
def rack_app
- app.app
+ app.rack_app
end
def path
@@ -45,7 +47,7 @@ module ActionDispatch
end
def engine?
- rack_app.respond_to?(:routes)
+ app.engine?
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 88deee5f5e..31eb6104fe 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/enumerable"
require "active_support/core_ext/array/extract_options"
@@ -306,7 +308,7 @@ module ActionDispatch
def check_controller_and_action(path_params, controller, action)
hash = check_part(:controller, controller, path_params, {}) do |part|
translate_controller(part) {
- message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
+ message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
raise ArgumentError, message
@@ -471,7 +473,17 @@ module ActionDispatch
# <tt>params[<:param>]</tt>.
# In your router:
#
- # resources :user, param: :name
+ # resources :users, param: :name
+ #
+ # The +users+ resource here will have the following routes generated for it:
+ #
+ # GET /users(.:format)
+ # POST /users(.:format)
+ # GET /users/new(.:format)
+ # GET /users/:name/edit(.:format)
+ # GET /users/:name(.:format)
+ # PATCH/PUT /users/:name(.:format)
+ # DELETE /users/:name(.:format)
#
# You can override <tt>ActiveRecord::Base#to_param</tt> of a related
# model to construct a URL:
@@ -482,8 +494,8 @@ module ActionDispatch
# end
# end
#
- # user = User.find_by(name: 'Phusion')
- # user_path(user) # => "/users/Phusion"
+ # user = User.find_by(name: 'Phusion')
+ # user_path(user) # => "/users/Phusion"
#
# [:path]
# The path prefix for the routes.
@@ -652,18 +664,30 @@ module ActionDispatch
def define_generate_prefix(app, name)
_route = @set.named_routes.get name
_routes = @set
- app.routes.define_mounted_helper(name)
+
+ script_namer = ->(options) do
+ prefix_options = options.slice(*_route.segment_keys)
+ prefix_options[:relative_url_root] = "".freeze
+
+ if options[:_recall]
+ prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
+ end
+
+ # We must actually delete prefix segment keys to avoid passing them to next url_for.
+ _route.segment_keys.each { |k| options.delete(k) }
+ _routes.url_helpers.send("#{name}_path", prefix_options)
+ end
+
+ app.routes.define_mounted_helper(name, script_namer)
+
app.routes.extend Module.new {
def optimize_routes_generation?; false; end
+
define_method :find_script_name do |options|
if options.key? :script_name
super(options)
else
- prefix_options = options.slice(*_route.segment_keys)
- prefix_options[:relative_url_root] = "".freeze
- # We must actually delete prefix segment keys to avoid passing them to next url_for.
- _route.segment_keys.each { |k| options.delete(k) }
- _routes.url_helpers.send("#{name}_path", prefix_options)
+ script_namer.call(options)
end
end
}
@@ -1251,7 +1275,7 @@ module ActionDispatch
# POST /profile
#
# === Options
- # Takes same options as +resources+.
+ # Takes same options as resources[rdoc-ref:#resources]
def resource(*resources, &block)
options = resources.extract_options!.dup
@@ -1316,7 +1340,7 @@ module ActionDispatch
# DELETE /photos/:photo_id/comments/:id
#
# === Options
- # Takes same options as <tt>Base#match</tt> as well as:
+ # Takes same options as match[rdoc-ref:Base#match] as well as:
#
# [:path_names]
# Allows you to change the segment component of the +edit+ and +new+ actions.
@@ -1549,7 +1573,7 @@ module ActionDispatch
# Matches a URL pattern to one or more routes.
# For more information, see match[rdoc-ref:Base#match].
#
- # match 'path' => 'controller#action', via: patch
+ # match 'path' => 'controller#action', via: :patch
# match 'path', to: 'controller#action', via: :post
# match 'path', 'otherpath', on: :member, via: :get
def match(path, *rest, &block)
@@ -1837,7 +1861,7 @@ module ActionDispatch
path_types.fetch(String, []).each do |_path|
route_options = options.dup
if _path && option_path
- raise ArgumentError, "Ambiguous route definition. Both :path and the route path where specified as strings."
+ raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
end
to = get_to_from_path(_path, to, route_options[:action])
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
@@ -2022,7 +2046,7 @@ module ActionDispatch
end
module CustomUrls
- # Define custom url helpers that will be added to the application's
+ # Define custom URL helpers that will be added to the application's
# routes. This allows you to override and/or replace the default behavior
# of routing helpers, e.g:
#
@@ -2042,11 +2066,11 @@ module ActionDispatch
# arguments for +url_for+ which will actually build the URL string. This can
# be one of the following:
#
- # * A string, which is treated as a generated URL
- # * A hash, e.g. { controller: "pages", action: "index" }
- # * An array, which is passed to `polymorphic_url`
- # * An Active Model instance
- # * An Active Model class
+ # * A string, which is treated as a generated URL
+ # * A hash, e.g. <tt>{ controller: "pages", action: "index" }</tt>
+ # * An array, which is passed to +polymorphic_url+
+ # * An Active Model instance
+ # * An Active Model class
#
# NOTE: Other URL helpers can be called in the block but be careful not to invoke
# your custom URL helper again otherwise it will result in a stack overflow error.
@@ -2058,9 +2082,9 @@ module ActionDispatch
# [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
# end
#
- # In this instance the +params+ object comes from the context in which the the
+ # In this instance the +params+ object comes from the context in which the
# block is executed, e.g. generating a URL inside a controller action or a view.
- # If the block is executed where there isn't a params object such as this:
+ # If the block is executed where there isn't a +params+ object such as this:
#
# Rails.application.routes.url_helpers.browse_path
#
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index e89ea8b21d..6da869c0c2 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Routing
# Polymorphic URL helpers are methods for smart resolution to a named route call when
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 3bcb341758..143a4b3d62 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/http/request"
require "active_support/core_ext/uri"
require "active_support/core_ext/array/extract_options"
@@ -140,7 +142,7 @@ module ActionDispatch
# get "/stories" => redirect("/posts")
#
# This will redirect the user, while ignoring certain parts of the request, including query string, etc.
- # `/stories`, `/stories?foo=bar`, etc all redirect to `/posts`.
+ # <tt>/stories</tt>, <tt>/stories?foo=bar</tt>, etc all redirect to <tt>/posts</tt>.
#
# You can also use interpolation in the supplied redirect argument:
#
@@ -173,8 +175,8 @@ module ActionDispatch
# get '/stories', to: redirect(path: '/posts')
#
# This will redirect the user, while changing only the specified parts of the request,
- # for example the `path` option in the last example.
- # `/stories`, `/stories?foo=bar`, redirect to `/posts` and `/posts?foo=bar` respectively.
+ # for example the +path+ option in the last example.
+ # <tt>/stories</tt>, <tt>/stories?foo=bar</tt>, redirect to <tt>/posts</tt> and <tt>/posts?foo=bar</tt> respectively.
#
# Finally, an object which responds to call can be supplied to redirect, allowing you to reuse
# common redirect routes. The call method must accept two arguments, params and request, and return
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 68bd6d806b..9eff30fa53 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,6 +1,9 @@
+# frozen_string_literal: true
+
require "action_dispatch/journey"
require "active_support/core_ext/object/to_query"
require "active_support/core_ext/hash/slice"
+require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/array/extract_options"
require "action_controller/metal/exceptions"
@@ -73,7 +76,6 @@ module ActionDispatch
@routes = {}
@path_helpers = Set.new
@url_helpers = Set.new
- @custom_helpers = Set.new
@url_helpers_module = Module.new
@path_helpers_module = Module.new
end
@@ -96,23 +98,9 @@ module ActionDispatch
@url_helpers_module.send :remove_method, helper
end
- @custom_helpers.each do |helper|
- path_name = :"#{helper}_path"
- url_name = :"#{helper}_url"
-
- if @path_helpers_module.method_defined?(path_name)
- @path_helpers_module.send :remove_method, path_name
- end
-
- if @url_helpers_module.method_defined?(url_name)
- @url_helpers_module.send :remove_method, url_name
- end
- end
-
@routes.clear
@path_helpers.clear
@url_helpers.clear
- @custom_helpers.clear
end
def add(name, route)
@@ -158,21 +146,29 @@ module ActionDispatch
routes.length
end
+ # Given a +name+, defines name_path and name_url helpers.
+ # Used by 'direct', 'resolve', and 'polymorphic' route helpers.
def add_url_helper(name, defaults, &block)
- @custom_helpers << name
helper = CustomUrlHelper.new(name, defaults, &block)
+ path_name = :"#{name}_path"
+ url_name = :"#{name}_url"
@path_helpers_module.module_eval do
- define_method(:"#{name}_path") do |*args|
+ define_method(path_name) do |*args|
helper.call(self, args, true)
end
end
@url_helpers_module.module_eval do
- define_method(:"#{name}_url") do |*args|
+ define_method(url_name) do |*args|
helper.call(self, args, false)
end
end
+
+ @path_helpers << path_name
+ @url_helpers << url_name
+
+ self
end
class UrlHelper
@@ -203,6 +199,16 @@ module ActionDispatch
if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
options = t.url_options.merge @options
options[:path] = optimized_helper(args)
+
+ original_script_name = options.delete(:original_script_name)
+ script_name = t._routes.find_script_name(options)
+
+ if original_script_name
+ script_name = original_script_name + script_name
+ end
+
+ options[:script_name] = script_name
+
url_strategy.call options
else
super
@@ -240,7 +246,7 @@ module ActionDispatch
missing_keys << missing_key
}
constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }]
- message = "No route matches #{constraints.inspect}"
+ message = "No route matches #{constraints.inspect}".dup
message << ", missing required keys: #{missing_keys.sort.inspect}"
raise ActionController::UrlGenerationError, message
@@ -456,7 +462,7 @@ module ActionDispatch
MountedHelpers
end
- def define_mounted_helper(name)
+ def define_mounted_helper(name, script_namer = nil)
return if MountedHelpers.method_defined?(name)
routes = self
@@ -464,7 +470,7 @@ module ActionDispatch
MountedHelpers.class_eval do
define_method "_#{name}" do
- RoutesProxy.new(routes, _routes_context, helpers)
+ RoutesProxy.new(routes, _routes_context, helpers, script_namer)
end
end
@@ -551,7 +557,7 @@ module ActionDispatch
# plus a singleton class method called _routes ...
included do
- singleton_class.send(:redefine_method, :_routes) { routes }
+ redefine_singleton_method(:_routes) { routes }
end
# And an instance method _routes. Note that
@@ -588,14 +594,14 @@ module ActionDispatch
if route.segment_keys.include?(:controller)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :controller segment in a route is deprecated and
- will be removed in Rails 5.2.
+ will be removed in Rails 6.0.
MSG
end
if route.segment_keys.include?(:action)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :action segment in a route is deprecated and
- will be removed in Rails 5.2.
+ will be removed in Rails 6.0.
MSG
end
@@ -846,6 +852,10 @@ module ActionDispatch
end
req = make_request(env)
+ recognize_path_with_request(req, path, extras)
+ end
+
+ def recognize_path_with_request(req, path, extras)
@router.recognize(req) do |route, params|
params.merge!(extras)
params.each do |key, value|
@@ -864,6 +874,9 @@ module ActionDispatch
end
return req.path_parameters
+ elsif app.matches?(req) && app.engine?
+ path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras)
+ return path_parameters
end
end
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
index c1423f770f..587a72729c 100644
--- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/array/extract_options"
module ActionDispatch
@@ -8,9 +10,10 @@ module ActionDispatch
attr_accessor :scope, :routes
alias :_routes :routes
- def initialize(routes, scope, helpers)
+ def initialize(routes, scope, helpers, script_namer = nil)
@routes, @scope = routes, scope
@helpers = helpers
+ @script_namer = script_namer
end
def url_options
@@ -29,7 +32,16 @@ module ActionDispatch
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args)
options = args.extract_options!
- args << url_options.merge((options || {}).symbolize_keys)
+ options = url_options.merge((options || {}).symbolize_keys)
+
+ if @script_namer
+ options[:script_name] = merge_script_names(
+ options[:script_name],
+ @script_namer.call(options)
+ )
+ end
+
+ args << options
@helpers.#{method}(*args)
end
RUBY
@@ -38,6 +50,20 @@ module ActionDispatch
super
end
end
+
+ # Keeps the part of the script name provided by the global
+ # context via ENV["SCRIPT_NAME"], which `mount` doesn't know
+ # about since it depends on the specific request, but use our
+ # script name resolver for the mount point dependent part.
+ def merge_script_names(previous_script_name, new_script_name)
+ return new_script_name unless previous_script_name
+
+ resolved_parts = new_script_name.count("/")
+ previous_parts = previous_script_name.count("/")
+ context_parts = previous_parts - resolved_parts + 1
+
+ (previous_script_name.split("/").slice(0, context_parts).join("/")) + new_script_name
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index a9bdefa775..fa345dccdf 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Routing
# In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse
@@ -107,7 +109,7 @@ module ActionDispatch
end
# Hook overridden in controller to add request information
- # with `default_url_options`. Application logic should not
+ # with +default_url_options+. Application logic should not
# go into url_options.
def url_options
default_url_options
@@ -153,7 +155,7 @@ module ActionDispatch
# Missing routes keys may be filled in from the current request's parameters
# (e.g. +:controller+, +:action+, +:id+ and any other parameters that are
# placed in the path). Given that the current action has been reached
- # through `GET /users/1`:
+ # through <tt>GET /users/1</tt>:
#
# url_for(only_path: true) # => '/users/1'
# url_for(only_path: true, action: 'edit') # => '/users/1/edit'
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index c39a135ce0..f85f816bb9 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -1,10 +1,16 @@
+# frozen_string_literal: true
+
+gem "capybara", "~> 2.15"
+
require "capybara/dsl"
require "capybara/minitest"
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
@@ -64,6 +70,9 @@ module ActionDispatch
# size of the browser screen. These two options are not applicable for
# headless drivers and will be silently ignored if passed.
#
+ # Headless browsers such as headless Chrome and headless Firefox are also supported.
+ # You can use these browsers by setting the +:using+ argument to +:headless_chrome+ or +:headless_firefox+.
+ #
# To use a headless driver, like Poltergeist, update your Gemfile to use
# Poltergeist instead of Selenium and then declare the driver name in the
# +application_system_test_case.rb+ file. In this case, you would leave out
@@ -88,6 +97,7 @@ module ActionDispatch
include Capybara::Minitest::Assertions
include SystemTesting::TestHelpers::SetupAndTeardown
include SystemTesting::TestHelpers::ScreenshotHelper
+ include SystemTesting::TestHelpers::UndefMethods
def initialize(*) # :nodoc:
super
@@ -115,14 +125,22 @@ module ActionDispatch
#
# driven_by :poltergeist
#
+ # driven_by :selenium, screen_size: [800, 800]
+ #
+ # driven_by :selenium, using: :chrome
+ #
+ # driven_by :selenium, using: :headless_chrome
+ #
# driven_by :selenium, using: :firefox
#
- # driven_by :selenium, screen_size: [800, 800]
+ # 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)
end
driven_by :selenium
+
+ ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self)
end
SystemTestCase.start_application
diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb
new file mode 100644
index 0000000000..10e6888ab3
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/browser.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module ActionDispatch
+ module SystemTesting
+ class Browser # :nodoc:
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def type
+ case name
+ when :headless_chrome
+ :chrome
+ when :headless_firefox
+ :firefox
+ else
+ name
+ end
+ end
+
+ def options
+ case name
+ when :headless_chrome
+ headless_chrome_browser_options
+ when :headless_firefox
+ headless_firefox_browser_options
+ end
+ end
+
+ private
+ def headless_chrome_browser_options
+ options = Selenium::WebDriver::Chrome::Options.new
+ options.args << "--headless"
+ options.args << "--disable-gpu"
+
+ options
+ end
+
+ def headless_firefox_browser_options
+ options = Selenium::WebDriver::Firefox::Options.new
+ options.args << "-headless"
+
+ options
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb
index 1a027f2e23..5252ff6746 100644
--- a/actionpack/lib/action_dispatch/system_testing/driver.rb
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -1,22 +1,24 @@
+# frozen_string_literal: true
+
module ActionDispatch
module SystemTesting
class Driver # :nodoc:
def initialize(name, **options)
@name = name
- @browser = options[:using]
+ @browser = Browser.new(options[:using])
@screen_size = options[:screen_size]
@options = options[:options]
end
def use
- register unless rack_test?
+ register if registerable?
setup
end
private
- def rack_test?
- @name == :rack_test
+ def registerable?
+ [:selenium, :poltergeist, :webkit].include?(@name)
end
def register
@@ -29,8 +31,12 @@ module ActionDispatch
end
end
+ def browser_options
+ @options.merge(options: @browser.options).compact
+ end
+
def register_selenium(app)
- Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver|
+ Capybara::Selenium::Driver.new(app, { browser: @browser.type }.merge(browser_options)).tap do |driver|
driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
end
end
@@ -41,7 +47,7 @@ module ActionDispatch
def register_webkit(app)
Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash.merge(@options)).tap do |driver|
- driver.resize_window(*@screen_size)
+ driver.resize_window_to(driver.current_window_handle, *@screen_size)
end
end
diff --git a/actionpack/lib/action_dispatch/system_testing/server.rb b/actionpack/lib/action_dispatch/system_testing/server.rb
index 4a214ef713..4fc1f33767 100644
--- a/actionpack/lib/action_dispatch/system_testing/server.rb
+++ b/actionpack/lib/action_dispatch/system_testing/server.rb
@@ -1,27 +1,26 @@
-require "rack/handler/puma"
+# frozen_string_literal: true
module ActionDispatch
module SystemTesting
class Server # :nodoc:
+ class << self
+ attr_accessor :silence_puma
+ end
+
+ self.silence_puma = false
+
def run
- register
setup
end
private
- def register
- Capybara.register_server :rails_puma do |app, port, host|
- Rack::Handler::Puma.run(app, Port: port, Threads: "0:1")
- end
- end
-
def setup
set_server
set_port
end
def set_server
- Capybara.server = :rails_puma
+ Capybara.server = :puma, { Silent: self.class.silence_puma } if Capybara.server == Capybara.servers[:default]
end
def set_port
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
index 859d68e475..df0c5d3f0e 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module SystemTesting
module TestHelpers
@@ -13,13 +15,12 @@ module ActionDispatch
#
# You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
# control the output. Possible values are:
- # * [+inline+ (default)] display the screenshot in the terminal using the
- # iTerm image protocol (http://iterm2.com/documentation-images.html).
- # * [+simple+] only display the screenshot path.
- # This is the default value if the +CI+ environment variables
- # is defined.
- # * [+artifact+] display the screenshot in the terminal, using the terminal
- # artifact format (http://buildkite.github.io/terminal/inline-images/).
+ # * [+simple+ (default)] Only displays the screenshot path.
+ # This is the default value.
+ # * [+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/).
def take_screenshot
save_image
puts display_image
@@ -42,35 +43,36 @@ module ActionDispatch
end
def image_path
- "tmp/screenshots/#{image_name}.png"
+ @image_path ||= absolute_image_path.relative_path_from(Pathname.pwd).to_s
+ end
+
+ def absolute_image_path
+ Rails.root.join("tmp/screenshots/#{image_name}.png")
end
def save_image
- page.save_screenshot(Rails.root.join(image_path))
+ page.save_screenshot(absolute_image_path)
end
def output_type
# Environment variables have priority
output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] || ENV["CAPYBARA_INLINE_SCREENSHOT"]
- # If running in a CI environment, default to simple
- output_type ||= "simple" if ENV["CI"]
-
- # Default
- output_type ||= "inline"
+ # Default to outputting a path to the screenshot
+ output_type ||= "simple"
output_type
end
def display_image
- message = "[Screenshot]: #{image_path}\n"
+ message = "[Screenshot]: #{image_path}\n".dup
case output_type
when "artifact"
- message << "\e]1338;url=artifact://#{image_path}\a\n"
+ message << "\e]1338;url=artifact://#{absolute_image_path}\a\n"
when "inline"
- name = inline_base64(File.basename(image_path))
- image = inline_base64(File.read(image_path))
+ name = inline_base64(File.basename(absolute_image_path))
+ image = inline_base64(File.read(absolute_image_path))
message << "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a\n"
end
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
index f03f0d4299..ffa85f4e14 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module SystemTesting
module TestHelpers
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb
new file mode 100644
index 0000000000..d64be3b3d9
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module ActionDispatch
+ module SystemTesting
+ module TestHelpers
+ module UndefMethods # :nodoc:
+ extend ActiveSupport::Concern
+ included do
+ METHODS = %i(get post put patch delete).freeze
+
+ METHODS.each do |verb|
+ undef_method verb
+ end
+
+ def method_missing(method, *args, &block)
+ if METHODS.include?(method)
+ raise NoMethodError, "System tests cannot make direct requests via ##{method}; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information."
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb
index c37726957e..dc019db6ac 100644
--- a/actionpack/lib/action_dispatch/testing/assertion_response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
# This is a class that abstracts away an asserted response. It purposely
# does not inherit from Response because it doesn't need it. That means it
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index 4ea18d671d..08c2969685 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails-dom-testing"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 1baf979ac9..98b1965d22 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -79,7 +81,7 @@ module ActionDispatch
def generate_response_message(expected, actual = @response.response_code)
"Expected response to be a <#{code_with_name(expected)}>,"\
" but was a <#{code_with_name(actual)}>"
- .concat(location_if_redirected).concat(response_body_if_short)
+ .dup.concat(location_if_redirected).concat(response_body_if_short)
end
def response_body_if_short
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 8645df4370..5390581139 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "uri"
require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/string/access"
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 2416c58817..7171b6942c 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "stringio"
require "uri"
require "active_support/core_ext/kernel/singleton_class"
@@ -10,38 +12,38 @@ require "action_dispatch/testing/request_encoder"
module ActionDispatch
module Integration #:nodoc:
module RequestHelpers
- # Performs a GET request with the given parameters. See +#process+ for more
- # details.
+ # Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def get(path, **args)
process(:get, path, **args)
end
- # Performs a POST request with the given parameters. See +#process+ for more
- # details.
+ # Performs a POST request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def post(path, **args)
process(:post, path, **args)
end
- # Performs a PATCH request with the given parameters. See +#process+ for more
- # details.
+ # Performs a PATCH request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def patch(path, **args)
process(:patch, path, **args)
end
- # Performs a PUT request with the given parameters. See +#process+ for more
- # details.
+ # Performs a PUT request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def put(path, **args)
process(:put, path, **args)
end
- # Performs a DELETE request with the given parameters. See +#process+ for
- # more details.
+ # Performs a DELETE request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def delete(path, **args)
process(:delete, path, **args)
end
- # Performs a HEAD request with the given parameters. See +#process+ for more
- # details.
+ # Performs a HEAD request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def head(path, *args)
process(:head, path, *args)
end
@@ -338,8 +340,7 @@ module ActionDispatch
@integration_session = nil
end
- %w(get post patch put head delete cookies assigns
- xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
+ %w(get post patch put head delete cookies assigns follow_redirect!).each do |method|
define_method(method) do |*args|
# reset the html_document variable, except for cookies/assigns calls
unless method == "cookies" || method == "assigns"
diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb
index 8c27e9ecb7..01246b7a2e 100644
--- a/actionpack/lib/action_dispatch/testing/request_encoder.rb
+++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActionDispatch
class RequestEncoder # :nodoc:
class IdentityEncoder
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 0282eb15c3..8ac50c730d 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/middleware/cookies"
require "action_dispatch/middleware/flash"
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index ec949c869b..6c5b7af50e 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/indifferent_access"
require "rack/utils"
@@ -9,7 +11,7 @@ module ActionDispatch
"HTTP_USER_AGENT" => "Rails Testing",
)
- # Create a new test request with default `env` values.
+ # Create a new test request with default +env+ values.
def self.create(env = {})
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
env["rack.request.cookie_hash"] ||= {}.with_indifferent_access
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 5c89f9c75e..1e6b21f235 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "action_dispatch/testing/request_encoder"
module ActionDispatch
@@ -18,13 +20,31 @@ module ActionDispatch
end
# Was the response successful?
- alias_method :success?, :successful?
+ def success?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ The success? predicate is deprecated and will be removed in Rails 6.0.
+ Please use successful? as provided by Rack::Response::Helpers.
+ MSG
+ successful?
+ end
# Was the URL not found?
- alias_method :missing?, :not_found?
+ def missing?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ The missing? predicate is deprecated and will be removed in Rails 6.0.
+ Please use not_found? as provided by Rack::Response::Helpers.
+ MSG
+ not_found?
+ end
# Was there a server-side error?
- alias_method :error?, :server_error?
+ def error?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ The error? predicate is deprecated and will be removed in Rails 6.0.
+ Please use server_error? as provided by Rack::Response::Helpers.
+ MSG
+ server_error?
+ end
def parsed_body
@parsed_body ||= @response_parser.call(body)