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.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb54
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/simulator.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb3
-rw-r--r--actionpack/lib/action_dispatch/journey/scanner.rb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb42
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb42
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb80
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb22
-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/missing_exact_template.html.erb19
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb3
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb3
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb9
-rw-r--r--actionpack/lib/action_dispatch/routing.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/endpoint.rb15
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb149
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb23
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb19
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb20
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb3
-rw-r--r--actionpack/lib/action_dispatch/system_testing/browser.rb49
-rw-r--r--actionpack/lib/action_dispatch/system_testing/driver.rb29
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb4
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb15
-rw-r--r--actionpack/lib/action_dispatch/testing/request_encoder.rb2
47 files changed, 524 insertions, 223 deletions
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index a8febc32b3..a7c7cfc1e5 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -202,13 +202,17 @@ module ActionDispatch
self._cache_control = _cache_control + ", #{control[:extras].join(', ')}"
end
else
- extras = control[:extras]
+ extras = control[:extras]
max_age = control[:max_age]
+ stale_while_revalidate = control[:stale_while_revalidate]
+ stale_if_error = control[:stale_if_error]
options = []
options << "max-age=#{max_age.to_i}" if max_age
options << (control[:public] ? PUBLIC : PRIVATE)
options << MUST_REVALIDATE if control[:must_revalidate]
+ options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
+ options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
options.concat(extras) if extras
self._cache_control = options.join(", ")
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index 4883e23d24..35041fd072 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -21,7 +21,8 @@ module ActionDispatch #:nodoc:
return response if policy_present?(headers)
if policy = request.content_security_policy
- headers[header_name(request)] = policy.build(request.controller_instance)
+ nonce = request.content_security_policy_nonce
+ headers[header_name(request)] = policy.build(request.controller_instance, nonce)
end
response
@@ -51,6 +52,8 @@ module ActionDispatch #:nodoc:
module Request
POLICY = "action_dispatch.content_security_policy".freeze
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
+ NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator".freeze
+ NONCE = "action_dispatch.content_security_policy_nonce".freeze
def content_security_policy
get_header(POLICY)
@@ -67,6 +70,30 @@ module ActionDispatch #:nodoc:
def content_security_policy_report_only=(value)
set_header(POLICY_REPORT_ONLY, value)
end
+
+ def content_security_policy_nonce_generator
+ get_header(NONCE_GENERATOR)
+ end
+
+ def content_security_policy_nonce_generator=(generator)
+ set_header(NONCE_GENERATOR, generator)
+ end
+
+ def content_security_policy_nonce
+ if content_security_policy_nonce_generator
+ if nonce = get_header(NONCE)
+ nonce
+ else
+ set_header(NONCE, generate_content_security_policy_nonce)
+ end
+ end
+ end
+
+ private
+
+ def generate_content_security_policy_nonce
+ content_security_policy_nonce_generator.call(self)
+ end
end
MAPPINGS = {
@@ -81,7 +108,9 @@ module ActionDispatch #:nodoc:
blob: "blob:",
filesystem: "filesystem:",
report_sample: "'report-sample'",
- strict_dynamic: "'strict-dynamic'"
+ strict_dynamic: "'strict-dynamic'",
+ ws: "ws:",
+ wss: "wss:"
}.freeze
DIRECTIVES = {
@@ -97,12 +126,15 @@ module ActionDispatch #:nodoc:
manifest_src: "manifest-src",
media_src: "media-src",
object_src: "object-src",
+ prefetch_src: "prefetch-src",
script_src: "script-src",
style_src: "style-src",
worker_src: "worker-src"
}.freeze
- private_constant :MAPPINGS, :DIRECTIVES
+ NONCE_DIRECTIVES = %w[script-src].freeze
+
+ private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
attr_reader :directives
@@ -171,8 +203,8 @@ module ActionDispatch #:nodoc:
end
end
- def build(context = nil)
- build_directives(context).compact.join("; ") + ";"
+ def build(context = nil, nonce = nil)
+ build_directives(context, nonce).compact.join("; ")
end
private
@@ -195,10 +227,14 @@ module ActionDispatch #:nodoc:
end
end
- def build_directives(context)
+ def build_directives(context, nonce)
@directives.map do |directive, sources|
if sources.is_a?(Array)
- "#{directive} #{build_directive(sources, context).join(' ')}"
+ if nonce && nonce_directive?(directive)
+ "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
+ else
+ "#{directive} #{build_directive(sources, context).join(' ')}"
+ end
elsif sources
directive
else
@@ -227,5 +263,9 @@ module ActionDispatch #:nodoc:
raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
end
end
+
+ def nonce_directive?(directive)
+ NONCE_DIRECTIVES.include?(directive)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index ec86b8bc47..ec012ad02d 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -9,8 +9,8 @@ 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, where the value or the key can be replaced using
- # String#replace or similar method.
+ # sub-hashes are passed to it, where the value or the key can be replaced using
+ # String#replace or similar methods.
#
# env["action_dispatch.parameter_filter"] = [:password]
# => replaces the value to all keys matching /password/i with "[FILTERED]"
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index c3c2a9d8c5..6c7d24d2d0 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -121,7 +121,7 @@ module ActionDispatch
# not contained within the headers hash.
def env_name(key)
key = key.to_s
- if key =~ HTTP_HEADER
+ if HTTP_HEADER.match?(key)
key = key.upcase.tr("-", "_")
key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index d2b2106845..295539281f 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -279,8 +279,6 @@ module Mime
def all?; false; end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :string, :synonyms
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 0b162dc7f1..827f022ca2 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -65,6 +65,11 @@ module ActionDispatch
@tempfile.path
end
+ # Shortcut for +tempfile.to_path+.
+ def to_path
+ @tempfile.to_path
+ end
+
# Shortcut for +tempfile.rewind+.
def rewind
@tempfile.rewind
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index f0344fd927..35ba44005a 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -274,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/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
index 8efe48d91c..002f6feb97 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
@@ -25,8 +25,6 @@ module ActionDispatch
state = tt.eclosure(0)
until input.eos?
sym = input.scan(%r([/.?]|[^/.?]+))
-
- # FIXME: tt.eclosure is not needed for the GTG
state = tt.eclosure(tt.move(state, sym))
end
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index 08b931a3cd..32f632800c 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -32,7 +32,7 @@ module ActionDispatch
end
def name
- left.tr "*:".freeze, "".freeze
+ -left.tr("*:", "")
end
def type
@@ -82,7 +82,7 @@ module ActionDispatch
def initialize(left)
super
@regexp = DEFAULT_EXP
- @name = left.tr "*:".freeze, "".freeze
+ @name = -left.tr("*:", "")
end
def default_regexp?
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 2d85a89a56..537f479ee5 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -90,7 +90,7 @@ module ActionDispatch
return @separator_re unless @matchers.key?(node)
re = @matchers[node]
- "(#{re})"
+ "(#{Regexp.union(re)})"
end
def visit_GROUP(node)
@@ -183,7 +183,7 @@ module ActionDispatch
node = node.to_sym
if @requirements.key?(node)
- re = /#{@requirements[node]}|/
+ re = /#{Regexp.union(@requirements[node])}|/
@offsets.push((re.match("").length - 1) + @offsets.last)
else
@offsets << @offsets.last
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
index 639c063495..c0377459d5 100644
--- a/actionpack/lib/action_dispatch/journey/routes.rb
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -51,11 +51,12 @@ module ActionDispatch
def ast
@ast ||= begin
asts = anchored_routes.map(&:ast)
- Nodes::Or.new(asts) unless asts.empty?
+ Nodes::Or.new(asts)
end
end
def simulator
+ return if ast.nil?
@simulator ||= begin
gtg = GTG::Builder.new(ast).transition_table
GTG::Simulator.new(gtg)
diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb
index 4ae77903fa..2a075862e9 100644
--- a/actionpack/lib/action_dispatch/journey/scanner.rb
+++ b/actionpack/lib/action_dispatch/journey/scanner.rb
@@ -34,6 +34,13 @@ module ActionDispatch
private
+ # takes advantage of String @- deduping capabilities in Ruby 2.5 upwards
+ # see: https://bugs.ruby-lang.org/issues/13077
+ def dedup_scan(regex)
+ r = @ss.scan(regex)
+ r ? -r : nil
+ end
+
def scan
case
# /
@@ -47,15 +54,15 @@ module ActionDispatch
[:OR, "|"]
when @ss.skip(/\./)
[:DOT, "."]
- when text = @ss.scan(/:\w+/)
+ when text = dedup_scan(/:\w+/)
[:SYMBOL, text]
- when text = @ss.scan(/\*\w+/)
+ when text = dedup_scan(/\*\w+/)
[:STAR, text]
when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
text.tr! "\\", ""
- [:LITERAL, text]
+ [:LITERAL, -text]
# any char
- when text = @ss.scan(/./)
+ when text = dedup_scan(/./)
[:LITERAL, text]
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index ea4156c972..c45d947904 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -338,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
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 511306eb0e..077a83b112 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -50,10 +50,18 @@ module ActionDispatch
end
end
- def initialize(app, routes_app = nil, response_format = :default)
+ cattr_reader :interceptors, instance_accessor: false, default: []
+
+ def self.register_interceptor(object = nil, &block)
+ interceptor = object || block
+ interceptors << interceptor
+ end
+
+ def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
@app = app
@routes_app = routes_app
@response_format = response_format
+ @interceptors = interceptors
end
def call(env)
@@ -67,12 +75,26 @@ module ActionDispatch
response
rescue Exception => exception
+ invoke_interceptors(request, exception)
raise exception unless request.show_exceptions?
render_exception(request, exception)
end
private
+ def invoke_interceptors(request, exception)
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
+
+ @interceptors.each do |interceptor|
+ begin
+ interceptor.call(request, exception)
+ rescue Exception
+ log_error(request, wrapper)
+ end
+ end
+ end
+
def render_exception(request, exception)
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
@@ -130,23 +152,13 @@ module ActionDispatch
end
def create_template(request, wrapper)
- traces = wrapper.traces
-
- trace_to_show = "Application Trace"
- if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error"
- trace_to_show = "Full Trace"
- end
-
- if source_to_show = traces[trace_to_show].first
- source_to_show_id = source_to_show[:id]
- end
-
DebugView.new([RESCUES_TEMPLATE_PATH],
request: request,
+ exception_wrapper: wrapper,
exception: wrapper.exception,
- traces: traces,
- show_source_idx: source_to_show_id,
- trace_to_show: trace_to_show,
+ traces: wrapper.traces,
+ show_source_idx: wrapper.source_to_show_id,
+ trace_to_show: wrapper.trace_to_show,
routes_inspector: routes_inspector(wrapper.exception),
source_extracts: wrapper.source_extracts,
line_number: wrapper.line_number,
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 4f69abfa6f..fb2b2bd3b0 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -12,6 +12,7 @@ module ActionDispatch
"ActionController::UnknownHttpMethod" => :method_not_allowed,
"ActionController::NotImplemented" => :not_implemented,
"ActionController::UnknownFormat" => :not_acceptable,
+ "ActionController::MissingExactTemplate" => :not_acceptable,
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
"ActionDispatch::Http::Parameters::ParseError" => :bad_request,
@@ -22,17 +23,20 @@ module ActionDispatch
)
cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
- "ActionView::MissingTemplate" => "missing_template",
- "ActionController::RoutingError" => "routing_error",
- "AbstractController::ActionNotFound" => "unknown_action",
- "ActionView::Template::Error" => "template_error"
+ "ActionView::MissingTemplate" => "missing_template",
+ "ActionController::RoutingError" => "routing_error",
+ "AbstractController::ActionNotFound" => "unknown_action",
+ "ActiveRecord::StatementInvalid" => "invalid_statement",
+ "ActionView::Template::Error" => "template_error",
+ "ActionController::MissingExactTemplate" => "missing_exact_template",
)
- attr_reader :backtrace_cleaner, :exception, :line_number, :file
+ attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
def initialize(backtrace_cleaner, exception)
@backtrace_cleaner = backtrace_cleaner
@exception = original_exception(exception)
+ @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
end
@@ -63,7 +67,11 @@ module ActionDispatch
full_trace_with_ids = []
full_trace.each_with_index do |trace, idx|
- trace_with_id = { id: idx, trace: trace }
+ trace_with_id = {
+ exception_object_id: @exception.object_id,
+ id: idx,
+ trace: trace
+ }
if application_trace.include?(trace)
application_trace_with_ids << trace_with_id
@@ -96,6 +104,18 @@ module ActionDispatch
end
end
+ def trace_to_show
+ if traces["Application Trace"].empty? && rescue_template != "routing_error"
+ "Full Trace"
+ else
+ "Application Trace"
+ end
+ end
+
+ def source_to_show_id
+ (traces[trace_to_show].first || {})[:id]
+ end
+
private
def backtrace
@@ -110,6 +130,16 @@ module ActionDispatch
end
end
+ def causes_for(exception)
+ return enum_for(__method__, exception) unless block_given?
+
+ yield exception while exception = exception.cause
+ end
+
+ def wrapped_causes_for(exception, backtrace_cleaner)
+ causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
+ end
+
def clean_backtrace(*args)
if backtrace_cleaner
backtrace_cleaner.clean(backtrace, *args)
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 3e11846778..fd05eec172 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -73,7 +73,7 @@ module ActionDispatch
end
end
- def reset_session # :nodoc
+ def reset_session # :nodoc:
super
self.flash = nil
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 805d3f2148..da2871b551 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -30,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/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 4ea96196d3..df680c1c5f 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -25,7 +25,7 @@ module ActionDispatch
# 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.
#
- # Configure your session store in <tt>config/initializers/session_store.rb</tt>:
+ # Configure your session store in an initializer:
#
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
#
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index ef633aadc6..190e54223e 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -15,6 +15,8 @@ module ActionDispatch
#
# config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
#
+ # Cookies will not be flagged as secure for excluded requests.
+ #
# 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.
@@ -26,8 +28,8 @@ module ActionDispatch
# Set +config.ssl_options+ with <tt>hsts: { ... }</tt> 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).
+ # required to qualify for browser preload lists is 1 year. Defaults to
+ # 1 year (recommended).
#
# * +subdomains+: Set to +true+ to tell the browser to apply these settings
# to all subdomains. This protects your cookies from interception by a
@@ -47,9 +49,8 @@ module ActionDispatch
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
+ # Default to 1 year, the minimum for browser preload lists.
+ HSTS_EXPIRES_IN = 31536000
def self.default_hsts_options
{ expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
@@ -72,7 +73,7 @@ module ActionDispatch
if request.ssl?
@app.call(env).tap do |status, headers, body|
set_hsts_header! headers
- flag_cookies_as_secure! headers if @secure_cookies
+ flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request)
end
else
return redirect_to_https request unless @exclude.call(request)
@@ -112,7 +113,7 @@ module ActionDispatch
cookies = cookies.split("\n".freeze)
headers["Set-Cookie".freeze] = cookies.map { |cookie|
- if cookie !~ /;\s*secure\s*(;|$)/i
+ if !/;\s*secure\s*(;|$)/i.match?(cookie)
"#{cookie}; secure"
else
cookie
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 23492e14eb..277074f216 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -16,7 +16,7 @@ module ActionDispatch
# does not exist, a 404 "File not Found" response will be returned.
class FileHandler
def initialize(root, index: "index", headers: {})
- @root = root.chomp("/")
+ @root = root.chomp("/").b
@file_server = ::Rack::File.new(@root, headers)
@index = index
end
@@ -35,15 +35,14 @@ module ActionDispatch
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
- path = File.join(@root, p.dup.force_encoding(Encoding::UTF_8))
+ path = File.join(@root, p.b)
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
false
end
-
}
- return ::Rack::Utils.escape_path(match)
+ return ::Rack::Utils.escape_path(match).b
end
end
@@ -69,7 +68,7 @@ module ActionDispatch
headers["Vary"] = "Accept-Encoding" if gzip_path
- return [status, headers, body]
+ [status, headers, body]
ensure
request.path_info = path
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
index e7b913bbe4..88a8e6ad83 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
@@ -1,6 +1,8 @@
-<% @source_extracts.each_with_index do |source_extract, index| %>
+<% error_index = local_assigns[:error_index] || 0 %>
+
+<% source_extracts.each_with_index do |source_extract, index| %>
<% if source_extract[:code] %>
- <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
+ <div class="source <%= "hidden" if show_source_idx != index %>" id="frame-source-<%= error_index %>-<%= index %>">
<div class="info">
Extracted source (around line <strong>#<%= source_extract[:line_number] %></strong>):
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
index ab57b11c7d..835ca8d260 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
@@ -1,52 +1,62 @@
-<% names = @traces.keys %>
+<% names = traces.keys %>
+<% error_index = local_assigns[:error_index] || 0 %>
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
-<div id="traces">
+<div id="traces-<%= error_index %>">
<% names.each do |name| %>
<%
- show = "show('#{name.gsub(/\s/, '-')}');"
- hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
+ show = "show('#{name.gsub(/\s/, '-')}-#{error_index}');"
+ hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}-#{error_index}');"}
%>
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
<% end %>
- <% @traces.each do |name, trace| %>
- <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;">
- <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
+ <% traces.each do |name, trace| %>
+ <div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
+ <code style="font-size: 11px;">
+ <% trace.each do |frame| %>
+ <a class="trace-frames trace-frames-<%= error_index %>" data-exception-object-id="<%= frame[:exception_object_id] %>" data-frame-id="<%= frame[:id] %>" href="#">
+ <%= frame[:trace] %>
+ </a>
+ <br>
+ <% end %>
+ </code>
</div>
<% end %>
<script type="text/javascript">
- var traceFrames = document.getElementsByClassName('trace-frames');
- var selectedFrame, currentSource = document.getElementById('frame-source-0');
-
- // Add click listeners for all stack frames
- for (var i = 0; i < traceFrames.length; i++) {
- traceFrames[i].addEventListener('click', function(e) {
- e.preventDefault();
- var target = e.target;
- var frame_id = target.dataset.frameId;
-
- if (selectedFrame) {
- selectedFrame.className = selectedFrame.className.replace("selected", "");
- }
-
- target.className += " selected";
- selectedFrame = target;
-
- // Change the extracted source code
- changeSourceExtract(frame_id);
- });
-
- function changeSourceExtract(frame_id) {
- var el = document.getElementById('frame-source-' + frame_id);
- if (currentSource && el) {
- currentSource.className += " hidden";
- el.className = el.className.replace(" hidden", "");
- currentSource = el;
+ (function() {
+ var traceFrames = document.getElementsByClassName('trace-frames-<%= error_index %>');
+ var selectedFrame, currentSource = document.getElementById('frame-source-<%= error_index %>-0');
+
+ // Add click listeners for all stack frames
+ for (var i = 0; i < traceFrames.length; i++) {
+ traceFrames[i].addEventListener('click', function(e) {
+ e.preventDefault();
+ var target = e.target;
+ var frame_id = target.dataset.frameId;
+
+ if (selectedFrame) {
+ selectedFrame.className = selectedFrame.className.replace("selected", "");
+ }
+
+ target.className += " selected";
+ selectedFrame = target;
+
+ // Change the extracted source code
+ changeSourceExtract(frame_id);
+ });
+
+ function changeSourceExtract(frame_id) {
+ var el = document.getElementById('frame-source-<%= error_index %>-' + frame_id);
+ if (currentSource && el) {
+ currentSource.className += " hidden";
+ el.className = el.className.replace(" hidden", "");
+ currentSource = el;
+ }
}
}
- }
+ })();
</script>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
index f154021ae6..bde26f46c2 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
@@ -10,7 +10,25 @@
<div id="container">
<h2><%= h @exception.message %></h2>
- <%= render template: "rescues/_source" %>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx, error_index: 0 %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show, error_index: 0 %>
+
+ <% if @exception.cause %>
+ <h2>Exception Causes</h2>
+ <% end %>
+
+ <% @exception_wrapper.wrapped_causes.each.with_index(1) do |wrapper, index| %>
+ <div class="details">
+ <a class="summary" href="#" style="color: #F0F0F0; text-decoration: none; background: #C52F24; border-bottom: none;" onclick="return toggle(<%= wrapper.exception.object_id %>)">
+ <%= wrapper.exception.class.name %>: <%= h wrapper.exception.message %>
+ </a>
+ </div>
+
+ <div id="<%= wrapper.exception.object_id %>" style="display: none;">
+ <%= render "rescues/source", source_extracts: wrapper.source_extracts, show_source_idx: wrapper.source_to_show_id, error_index: index %>
+ <%= render "rescues/trace", traces: wrapper.traces, trace_to_show: wrapper.trace_to_show, error_index: index %>
+ </div>
+ <% end %>
+
<%= render template: "rescues/_request_and_response" %>
</div>
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..e8454acfad
--- /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: rails active_storage:install
+ <% end %>
+ </h2>
+
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
+ <%= 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..e5e3196710
--- /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: 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/missing_exact_template.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb
new file mode 100644
index 0000000000..76ab1691b5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb
@@ -0,0 +1,19 @@
+<header>
+ <h1>No template for interactive request</h1>
+</header>
+
+<div id="container">
+ <h2><%= h @exception.message %></h2>
+
+ <p class="summary">
+ <strong>NOTE!</strong><br>
+ Unless told otherwise, Rails expects an action to render a template with the same name,<br>
+ contained in a folder named after its controller.
+
+ If this controller is an API responding with 204 (No Content), <br>
+ which does not require a template,
+ then this error will occur when trying to access it via browser,<br>
+ since we expect an HTML template
+ to be rendered for such requests. If that's the case, carry on.
+ </p>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb
new file mode 100644
index 0000000000..fcdbe6069d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb
@@ -0,0 +1,3 @@
+Missing exact template
+
+<%= @exception.message %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
index 2a65fd06ad..22eb6e9b4e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
@@ -5,7 +5,7 @@
<div id="container">
<h2><%= h @exception.message %></h2>
- <%= render template: "rescues/_source" %>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
<%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
index 55dd5ddc7b..2b8f3f2a5e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
@@ -14,7 +14,7 @@
</p>
<% end %>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
<% if @routes_inspector %>
<h2>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
index 5060da9369..324ef1567a 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
@@ -11,10 +11,10 @@
</p>
<pre><code><%= h @exception.message %></code></pre>
- <%= render template: "rescues/_source" %>
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
<p><%= @exception.sub_template_message %></p>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
<%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 95e99987a0..eb6fbca6ba 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -28,7 +28,8 @@ module ActionDispatch
"X-XSS-Protection" => "1; mode=block",
"X-Content-Type-Options" => "nosniff",
"X-Download-Options" => "noopen",
- "X-Permitted-Cross-Domain-Policies" => "none"
+ "X-Permitted-Cross-Domain-Policies" => "none",
+ "Referrer-Policy" => "strict-origin-when-cross-origin"
}
config.action_dispatch.cookies_rotations = ActiveSupport::Messages::RotationConfiguration.new
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index d86d0b10c2..bc5e0670e0 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -93,6 +93,14 @@ module ActionDispatch
@delegate[key.to_s]
end
+ # Returns the nested value specified by the sequence of keys, returning
+ # +nil+ if any intermediate step is +nil+.
+ def dig(*keys)
+ load_for_read!
+ keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key }
+ @delegate.dig(*keys)
+ end
+
# Returns true if the session has the given key or false.
def has_key?(key)
load_for_read!
@@ -130,6 +138,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/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 72f7407c6e..5cde677051 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -243,8 +243,9 @@ module ActionDispatch
#
# rails routes
#
- # Target specific controllers by prefixing the command with <tt>-c</tt> option.
- #
+ # Target a specific controller with <tt>-c</tt>, or grep routes
+ # using <tt>-g</tt>. Useful in conjunction with <tt>--expanded</tt>
+ # which displays routes vertically.
module Routing
extend ActiveSupport::Autoload
diff --git a/actionpack/lib/action_dispatch/routing/endpoint.rb b/actionpack/lib/action_dispatch/routing/endpoint.rb
index 24dced1efd..28bb20d688 100644
--- a/actionpack/lib/action_dispatch/routing/endpoint.rb
+++ b/actionpack/lib/action_dispatch/routing/endpoint.rb
@@ -3,12 +3,15 @@
module ActionDispatch
module Routing
class Endpoint # :nodoc:
- 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
+ def dispatcher?; false; end
+ def redirect?; false; end
+ def matches?(req); true; end
+ def app; self; end
+ def rack_app; app; end
+
+ def engine?
+ rack_app.is_a?(Class) && rack_app < Rails::Engine
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index a2205569b4..cba49d1a0b 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "delegate"
-require "active_support/core_ext/string/strip"
+require "io/console/size"
module ActionDispatch
module Routing
@@ -61,11 +61,11 @@ module ActionDispatch
@routes = routes
end
- def format(formatter, filter = nil)
+ def format(formatter, filter = {})
routes_to_display = filter_routes(normalize_filter(filter))
routes = collect_routes(routes_to_display)
if routes.none?
- formatter.no_routes(collect_routes(@routes))
+ formatter.no_routes(collect_routes(@routes), filter)
return formatter.result
end
@@ -81,12 +81,12 @@ module ActionDispatch
end
private
-
def normalize_filter(filter)
- if filter.is_a?(Hash) && filter[:controller]
+ if filter[:controller]
{ controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ }
- elsif filter
- { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ }
+ elsif filter[:grep]
+ { controller: /#{filter[:grep]}/, action: /#{filter[:grep]}/,
+ verb: /#{filter[:grep]}/, name: /#{filter[:grep]}/, path: /#{filter[:grep]}/ }
end
end
@@ -126,62 +126,111 @@ module ActionDispatch
end
end
- class ConsoleFormatter
- def initialize
- @buffer = []
- end
+ module ConsoleFormatter
+ class Base
+ def initialize
+ @buffer = []
+ end
- def result
- @buffer.join("\n")
- end
+ def result
+ @buffer.join("\n")
+ end
- def section_title(title)
- @buffer << "\n#{title}:"
- end
+ def section_title(title)
+ end
- def section(routes)
- @buffer << draw_section(routes)
- end
+ def section(routes)
+ end
- def header(routes)
- @buffer << draw_header(routes)
- end
+ def header(routes)
+ end
- def no_routes(routes)
- @buffer <<
- if routes.none?
- <<-MESSAGE.strip_heredoc
- You don't have any routes defined!
+ def no_routes(routes, filter)
+ @buffer <<
+ if routes.none?
+ <<~MESSAGE
+ You don't have any routes defined!
+
+ Please add some routes in config/routes.rb.
+ MESSAGE
+ elsif filter.key?(:controller)
+ "No routes were found for this controller."
+ elsif filter.key?(:grep)
+ "No routes were found for this grep pattern."
+ end
- Please add some routes in config/routes.rb.
- MESSAGE
- else
- "No routes were found for this controller"
+ @buffer << "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
end
- @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
end
- private
- def draw_section(routes)
- header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
- name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
+ class Sheet < Base
+ def section_title(title)
+ @buffer << "\n#{title}:"
+ end
- routes.map do |r|
- "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
- end
+ def section(routes)
+ @buffer << draw_section(routes)
+ end
+
+ def header(routes)
+ @buffer << draw_header(routes)
end
- def draw_header(routes)
- name_width, verb_width, path_width = widths(routes)
+ private
+
+ def draw_section(routes)
+ header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
+ name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)
+
+ routes.map do |r|
+ "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
+ end
+ end
+
+ def draw_header(routes)
+ name_width, verb_width, path_width = widths(routes)
+
+ "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
+ end
+
+ def widths(routes)
+ [routes.map { |r| r[:name].length }.max || 0,
+ routes.map { |r| r[:verb].length }.max || 0,
+ routes.map { |r| r[:path].length }.max || 0]
+ end
+ end
- "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
+ class Expanded < Base
+ def section_title(title)
+ @buffer << "\n#{"[ #{title} ]"}"
end
- def widths(routes)
- [routes.map { |r| r[:name].length }.max || 0,
- routes.map { |r| r[:verb].length }.max || 0,
- routes.map { |r| r[:path].length }.max || 0]
+ def section(routes)
+ @buffer << draw_expanded_section(routes)
end
+
+ private
+
+ def draw_expanded_section(routes)
+ routes.map.each_with_index do |r, i|
+ <<~MESSAGE.chomp
+ #{route_header(index: i + 1)}
+ Prefix | #{r[:name]}
+ Verb | #{r[:verb]}
+ URI | #{r[:path]}
+ Controller#Action | #{r[:reqs]}
+ MESSAGE
+ end
+ end
+
+ def route_header(index:)
+ console_width = IO.console_size.second
+ header_prefix = "--[ Route #{index} ]"
+ dash_remainder = [console_width - header_prefix.size, 0].max
+
+ "#{header_prefix}#{'-' * dash_remainder}"
+ end
+ end
end
class HtmlTableFormatter
@@ -203,16 +252,16 @@ module ActionDispatch
end
def no_routes(*)
- @buffer << <<-MESSAGE.strip_heredoc
+ @buffer << <<~MESSAGE
<p>You don't have any routes defined!</p>
<ul>
<li>Please add some routes in <tt>config/routes.rb</tt>.</li>
<li>
For more information about routes, please see the Rails guide
- <a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
+ <a href="https://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
</li>
</ul>
- MESSAGE
+ MESSAGE
end
def result
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index d87a23a58c..ff325afc54 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -279,7 +279,7 @@ module ActionDispatch
def verify_regexp_requirements(requirements)
requirements.each do |requirement|
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
+ if ANCHOR_CHARACTERS_REGEX.match?(requirement.source)
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
end
@@ -309,7 +309,7 @@ module ActionDispatch
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.".dup
- message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
+ message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
raise ArgumentError, message
}
@@ -333,7 +333,7 @@ module ActionDispatch
end
def split_to(to)
- if to =~ /#/
+ if /#/.match?(to)
to.split("#")
else
[]
@@ -342,7 +342,7 @@ module ActionDispatch
def add_controller_module(controller, modyoule)
if modyoule && !controller.is_a?(Regexp)
- if controller =~ %r{\A/}
+ if %r{\A/}.match?(controller)
controller[1..-1]
else
[modyoule, controller].compact.join("/")
@@ -611,7 +611,7 @@ module ActionDispatch
end
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
- raise ArgumentError, <<-MSG.strip_heredoc unless path
+ raise ArgumentError, <<~MSG unless path
Must be called with mount point
mount SomeRackApp, at: "some_route"
@@ -664,6 +664,7 @@ module ActionDispatch
def define_generate_prefix(app, name)
_route = @set.named_routes.get name
_routes = @set
+ _url_helpers = @set.url_helpers
script_namer = ->(options) do
prefix_options = options.slice(*_route.segment_keys)
@@ -675,7 +676,7 @@ module ActionDispatch
# 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)
+ _url_helpers.send("#{name}_path", prefix_options)
end
app.routes.define_mounted_helper(name, script_namer)
@@ -1573,7 +1574,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)
@@ -1587,7 +1588,7 @@ module ActionDispatch
when Symbol
options[:action] = to
when String
- if to =~ /#/
+ if /#/.match?(to)
options[:to] = to
else
options[:controller] = to
@@ -1913,7 +1914,7 @@ module ActionDispatch
default_action = options.delete(:action) || @scope[:action]
- if action =~ /^[\w\-\/]+$/
+ if /^[\w\-\/]+$/.match?(action)
default_action ||= action.tr("-", "_") unless action.include?("/")
else
action = nil
@@ -2082,9 +2083,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 6da869c0c2..e17ccaf986 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -120,8 +120,7 @@ module ActionDispatch
opts
end
- # Returns the path component of a URL for the given record. It uses
- # <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
+ # Returns the path component of a URL for the given record.
def polymorphic_path(record_or_hash_or_array, options = {})
if Hash === record_or_hash_or_array
options = record_or_hash_or_array.merge(options)
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 9eff30fa53..07d3a41173 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -2,7 +2,6 @@
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"
@@ -36,7 +35,7 @@ module ActionDispatch
if @raise_on_name_error
raise
else
- return [404, { "X-Cascade" => "pass" }, []]
+ [404, { "X-Cascade" => "pass" }, []]
end
end
@@ -154,13 +153,13 @@ module ActionDispatch
url_name = :"#{name}_url"
@path_helpers_module.module_eval do
- define_method(path_name) do |*args|
+ redefine_method(path_name) do |*args|
helper.call(self, args, true)
end
end
@url_helpers_module.module_eval do
- define_method(url_name) do |*args|
+ redefine_method(url_name) do |*args|
helper.call(self, args, false)
end
end
@@ -585,7 +584,7 @@ module ActionDispatch
"You may have defined two routes with the same name using the `:as` option, or " \
"you may be overriding a route already defined by a resource with the same naming. " \
"For the latter, you can restrict the routes created with `resources` as explained here: \n" \
- "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
+ "https://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
end
route = @set.add_route(name, mapping)
@@ -855,7 +854,7 @@ module ActionDispatch
recognize_path_with_request(req, path, extras)
end
- def recognize_path_with_request(req, path, extras)
+ def recognize_path_with_request(req, path, extras, raise_on_missing: true)
@router.recognize(req) do |route, params|
params.merge!(extras)
params.each do |key, value|
@@ -875,12 +874,14 @@ module ActionDispatch
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
+ path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras, raise_on_missing: false)
+ return path_parameters if path_parameters
end
end
- raise ActionController::RoutingError, "No route matches #{path.inspect}"
+ if raise_on_missing
+ raise ActionController::RoutingError, "No route matches #{path.inspect}"
+ end
end
end
# :startdoc:
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index fa345dccdf..1a31c7dbb8 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -191,7 +191,25 @@ module ActionDispatch
end
end
- def route_for(name, *args) # :nodoc:
+ # Allows calling direct or regular named route.
+ #
+ # resources :buckets
+ #
+ # direct :recordable do |recording|
+ # route_for(:bucket, recording.bucket)
+ # end
+ #
+ # direct :threadable do |threadable|
+ # route_for(:recordable, threadable.parent)
+ # end
+ #
+ # This maintains the context of the original caller on
+ # whether to return a path or full URL, e.g:
+ #
+ # threadable_path(threadable) # => "/buckets/1"
+ # threadable_url(threadable) # => "http://example.com/buckets/1"
+ #
+ def route_for(name, *args)
public_send(:"#{name}_url", *args)
end
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index 393141535b..c74c0ccced 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
-gem "capybara", "~> 2.15"
+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"
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..1b0bce6b9e
--- /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" if Gem.win_platform?
+
+ 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 280989a146..5252ff6746 100644
--- a/actionpack/lib/action_dispatch/system_testing/driver.rb
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -5,7 +5,7 @@ module ActionDispatch
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
@@ -32,34 +32,11 @@ module ActionDispatch
end
def browser_options
- if @browser == :headless_chrome
- browser_options = Selenium::WebDriver::Chrome::Options.new
- browser_options.args << "--headless"
- browser_options.args << "--disable-gpu"
-
- @options.merge(options: browser_options)
- elsif @browser == :headless_firefox
- browser_options = Selenium::WebDriver::Firefox::Options.new
- browser_options.args << "-headless"
-
- @options.merge(options: browser_options)
- else
- @options
- end
- end
-
- def browser
- if @browser == :headless_chrome
- :chrome
- elsif @browser == :headless_firefox
- :firefox
- else
- @browser
- end
+ @options.merge(options: @browser.options).compact
end
def register_selenium(app)
- Capybara::Selenium::Driver.new(app, { browser: browser }.merge(browser_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
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 df0c5d3f0e..d2685e0452 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
@@ -43,7 +43,7 @@ module ActionDispatch
end
def image_path
- @image_path ||= absolute_image_path.relative_path_from(Pathname.pwd).to_s
+ @image_path ||= absolute_image_path.to_s
end
def absolute_image_path
@@ -80,7 +80,7 @@ module ActionDispatch
end
def inline_base64(path)
- Base64.encode64(path).gsub("\n", "")
+ Base64.strict_encode64(path)
end
def failed?
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 ffa85f4e14..e47d5020f4 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
@@ -19,6 +19,7 @@ module ActionDispatch
def after_teardown
take_failed_screenshot
Capybara.reset_sessions!
+ ensure
super
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 5390581139..77cb311630 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -78,7 +78,7 @@ module ActionDispatch
# # Asserts that the generated route gives us our custom route
# assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil)
- if expected_path =~ %r{://}
+ if %r{://}.match?(expected_path)
fail_on(URI::InvalidURIError, message) do
uri = URI.parse(expected_path)
expected_path = uri.path.to_s.empty? ? "/" : uri.path
@@ -189,7 +189,7 @@ module ActionDispatch
request = ActionController::TestRequest.create @controller.class
- if path =~ %r{://}
+ if %r{://}.match?(path)
fail_on(URI::InvalidURIError, msg) do
uri = URI.parse(path)
request.env["rack.url_scheme"] = uri.scheme || "http"
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 7171b6942c..45439a3bb1 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -50,10 +50,11 @@ module ActionDispatch
# Follow a single redirect response. If the last response was not a
# redirect, an exception will be raised. Otherwise, the redirect is
- # performed on the location header.
- def follow_redirect!
+ # performed on the location header. Any arguments are passed to the
+ # underlying call to `get`.
+ def follow_redirect!(**args)
raise "not a redirect! #{status} #{status_message}" unless redirect?
- get(response.location)
+ get(response.location, **args)
status
end
end
@@ -189,6 +190,12 @@ module ActionDispatch
# merged into the Rack env hash.
# - +env+: Additional env to pass, as a Hash. The headers will be
# merged into the Rack env hash.
+ # - +xhr+: Set to `true` if you want to make and Ajax request.
+ # Adds request headers characteristic of XMLHttpRequest e.g. HTTP_X_REQUESTED_WITH.
+ # The headers will be merged into the Rack env hash.
+ # - +as+: Used for encoding the request with different content type.
+ # Supports `:json` by default and will set the approriate request headers.
+ # The headers will be merged into the Rack env hash.
#
# This method is rarely used directly. Use +#get+, +#post+, or other standard
# HTTP methods in integration tests. +#process+ is only required when using a
@@ -210,7 +217,7 @@ module ActionDispatch
method = :post
end
- if path =~ %r{://}
+ if %r{://}.match?(path)
path = build_expanded_path(path) do |location|
https! URI::HTTPS === location if location.scheme
diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb
index 01246b7a2e..9889f61951 100644
--- a/actionpack/lib/action_dispatch/testing/request_encoder.rb
+++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb
@@ -34,7 +34,7 @@ module ActionDispatch
end
def encode_params(params)
- @param_encoder.call(params)
+ @param_encoder.call(params) if params
end
def self.parser(content_type)