actionpack 7.0.0.alpha2 → 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +139 -0
  3. data/lib/abstract_controller/callbacks.rb +15 -2
  4. data/lib/abstract_controller/translation.rb +4 -1
  5. data/lib/action_controller/log_subscriber.rb +1 -2
  6. data/lib/action_controller/metal/helpers.rb +1 -1
  7. data/lib/action_controller/metal/http_authentication.rb +2 -1
  8. data/lib/action_controller/metal/instrumentation.rb +2 -0
  9. data/lib/action_controller/metal/params_wrapper.rb +13 -4
  10. data/lib/action_controller/metal/redirecting.rb +58 -22
  11. data/lib/action_controller/metal/request_forgery_protection.rb +30 -34
  12. data/lib/action_controller/metal/strong_parameters.rb +60 -19
  13. data/lib/action_controller/railtie.rb +16 -10
  14. data/lib/action_controller/test_case.rb +13 -2
  15. data/lib/action_controller.rb +0 -1
  16. data/lib/action_dispatch/http/response.rb +0 -12
  17. data/lib/action_dispatch/http/url.rb +2 -9
  18. data/lib/action_dispatch/journey/nodes/node.rb +2 -2
  19. data/lib/action_dispatch/journey/route.rb +1 -1
  20. data/lib/action_dispatch/middleware/cookies.rb +1 -1
  21. data/lib/action_dispatch/middleware/executor.rb +3 -0
  22. data/lib/action_dispatch/middleware/host_authorization.rb +41 -21
  23. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  24. data/lib/action_dispatch/middleware/show_exceptions.rb +10 -0
  25. data/lib/action_dispatch/middleware/static.rb +0 -1
  26. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  27. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +2 -0
  28. data/lib/action_dispatch/routing/inspector.rb +1 -1
  29. data/lib/action_dispatch/routing/mapper.rb +10 -6
  30. data/lib/action_dispatch/routing/route_set.rb +5 -0
  31. data/lib/action_dispatch/system_test_case.rb +7 -1
  32. data/lib/action_dispatch/system_testing/browser.rb +2 -12
  33. data/lib/action_dispatch/system_testing/driver.rb +13 -9
  34. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
  35. data/lib/action_dispatch/testing/test_process.rb +1 -27
  36. data/lib/action_dispatch.rb +1 -0
  37. data/lib/action_pack/gem_version.rb +1 -1
  38. metadata +14 -13
  39. data/lib/action_controller/metal/query_tags.rb +0 -16
@@ -81,7 +81,7 @@ module ActionController
81
81
  # })
82
82
  #
83
83
  # permitted = params.require(:person).permit(:name, :age)
84
- # permitted # => <ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
84
+ # permitted # => #<ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
85
85
  # permitted.permitted? # => true
86
86
  #
87
87
  # Person.first.update!(permitted)
@@ -111,7 +111,7 @@ module ActionController
111
111
  #
112
112
  # params = ActionController::Parameters.new(a: "123", b: "456")
113
113
  # params.permit(:c)
114
- # # => <ActionController::Parameters {} permitted: true>
114
+ # # => #<ActionController::Parameters {} permitted: true>
115
115
  #
116
116
  # ActionController::Parameters.action_on_unpermitted_parameters = :raise
117
117
  #
@@ -442,7 +442,7 @@ module ActionController
442
442
  # either present or the singleton +false+, returns said value:
443
443
  #
444
444
  # ActionController::Parameters.new(person: { name: "Francesco" }).require(:person)
445
- # # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
445
+ # # => #<ActionController::Parameters {"name"=>"Francesco"} permitted: false>
446
446
  #
447
447
  # Otherwise raises <tt>ActionController::ParameterMissing</tt>:
448
448
  #
@@ -570,13 +570,48 @@ module ActionController
570
570
  # })
571
571
  #
572
572
  # params.require(:person).permit(:contact)
573
- # # => <ActionController::Parameters {} permitted: true>
573
+ # # => #<ActionController::Parameters {} permitted: true>
574
574
  #
575
575
  # params.require(:person).permit(contact: :phone)
576
- # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
576
+ # # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
577
577
  #
578
578
  # params.require(:person).permit(contact: [ :email, :phone ])
579
- # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
579
+ # # => #<ActionController::Parameters {"contact"=>#<ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
580
+ #
581
+ # If your parameters specify multiple parameters indexed by a number,
582
+ # you can permit each set of parameters under the numeric key to be the same using the same syntax as permitting a single item.
583
+ #
584
+ # params = ActionController::Parameters.new({
585
+ # person: {
586
+ # '0': {
587
+ # email: "none@test.com",
588
+ # phone: "555-1234"
589
+ # },
590
+ # '1': {
591
+ # email: "nothing@test.com",
592
+ # phone: "555-6789"
593
+ # },
594
+ # }
595
+ # })
596
+ # params.permit(person: [:email]).to_h
597
+ # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"email"=>"nothing@test.com"}}}
598
+ #
599
+ # If you want to specify what keys you want from each numeric key, you can instead specify each one individually
600
+ #
601
+ # params = ActionController::Parameters.new({
602
+ # person: {
603
+ # '0': {
604
+ # email: "none@test.com",
605
+ # phone: "555-1234"
606
+ # },
607
+ # '1': {
608
+ # email: "nothing@test.com",
609
+ # phone: "555-6789"
610
+ # },
611
+ # }
612
+ # })
613
+ # params.permit(person: { '0': [:email], '1': [:phone]}).to_h
614
+ # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}}
580
615
  def permit(*filters)
581
616
  params = self.class.new
582
617
 
@@ -598,7 +633,7 @@ module ActionController
598
633
  # returns +nil+.
599
634
  #
600
635
  # params = ActionController::Parameters.new(person: { name: "Francesco" })
601
- # params[:person] # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
636
+ # params[:person] # => #<ActionController::Parameters {"name"=>"Francesco"} permitted: false>
602
637
  # params[:none] # => nil
603
638
  def [](key)
604
639
  convert_hashes_to_parameters(key, @parameters[key])
@@ -618,9 +653,9 @@ module ActionController
618
653
  # is given, then that will be run and its result returned.
619
654
  #
620
655
  # params = ActionController::Parameters.new(person: { name: "Francesco" })
621
- # params.fetch(:person) # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
656
+ # params.fetch(:person) # => #<ActionController::Parameters {"name"=>"Francesco"} permitted: false>
622
657
  # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
623
- # params.fetch(:none, {}) # => <ActionController::Parameters {} permitted: false>
658
+ # params.fetch(:none, {}) # => #<ActionController::Parameters {} permitted: false>
624
659
  # params.fetch(:none, "Francesco") # => "Francesco"
625
660
  # params.fetch(:none) { "Francesco" } # => "Francesco"
626
661
  def fetch(key, *args)
@@ -654,8 +689,8 @@ module ActionController
654
689
  # don't exist, returns an empty hash.
655
690
  #
656
691
  # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
657
- # params.slice(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
658
- # params.slice(:d) # => <ActionController::Parameters {} permitted: false>
692
+ # params.slice(:a, :b) # => #<ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
693
+ # params.slice(:d) # => #<ActionController::Parameters {} permitted: false>
659
694
  def slice(*keys)
660
695
  new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
661
696
  end
@@ -671,8 +706,8 @@ module ActionController
671
706
  # filters out the given +keys+.
672
707
  #
673
708
  # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
674
- # params.except(:a, :b) # => <ActionController::Parameters {"c"=>3} permitted: false>
675
- # params.except(:d) # => <ActionController::Parameters {"a"=>1, "b"=>2, "c"=>3} permitted: false>
709
+ # params.except(:a, :b) # => #<ActionController::Parameters {"c"=>3} permitted: false>
710
+ # params.except(:d) # => #<ActionController::Parameters {"a"=>1, "b"=>2, "c"=>3} permitted: false>
676
711
  def except(*keys)
677
712
  new_instance_with_inherited_permitted_status(@parameters.except(*keys))
678
713
  end
@@ -680,8 +715,8 @@ module ActionController
680
715
  # Removes and returns the key/value pairs matching the given keys.
681
716
  #
682
717
  # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
683
- # params.extract!(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
684
- # params # => <ActionController::Parameters {"c"=>3} permitted: false>
718
+ # params.extract!(:a, :b) # => #<ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
719
+ # params # => #<ActionController::Parameters {"c"=>3} permitted: false>
685
720
  def extract!(*keys)
686
721
  new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
687
722
  end
@@ -691,7 +726,7 @@ module ActionController
691
726
  #
692
727
  # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
693
728
  # params.transform_values { |x| x * 2 }
694
- # # => <ActionController::Parameters {"a"=>2, "b"=>4, "c"=>6} permitted: false>
729
+ # # => #<ActionController::Parameters {"a"=>2, "b"=>4, "c"=>6} permitted: false>
695
730
  def transform_values
696
731
  return to_enum(:transform_values) unless block_given?
697
732
  new_instance_with_inherited_permitted_status(
@@ -937,12 +972,18 @@ module ActionController
937
972
  end
938
973
  end
939
974
 
940
- def each_element(object, &block)
975
+ def specify_numeric_keys?(filter)
976
+ if filter.respond_to?(:keys)
977
+ filter.keys.any? { |key| /\A-?\d+\z/.match?(key) }
978
+ end
979
+ end
980
+
981
+ def each_element(object, filter, &block)
941
982
  case object
942
983
  when Array
943
984
  object.grep(Parameters).filter_map(&block)
944
985
  when Parameters
945
- if object.nested_attributes?
986
+ if object.nested_attributes? && !specify_numeric_keys?(filter)
946
987
  object.each_nested_attribute(&block)
947
988
  else
948
989
  yield object
@@ -1055,7 +1096,7 @@ module ActionController
1055
1096
  end
1056
1097
  elsif non_scalar?(value)
1057
1098
  # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
1058
- params[key] = each_element(value) do |element|
1099
+ params[key] = each_element(value, filter[key]) do |element|
1059
1100
  element.permit(*Array.wrap(filter[key]))
1060
1101
  end
1061
1102
  end
@@ -12,6 +12,7 @@ module ActionController
12
12
  config.action_controller = ActiveSupport::OrderedOptions.new
13
13
  config.action_controller.raise_on_open_redirects = false
14
14
  config.action_controller.log_query_tags_around_actions = true
15
+ config.action_controller.wrap_parameters_by_default = false
15
16
 
16
17
  config.eager_load_namespaces << ActionController
17
18
 
@@ -62,15 +63,18 @@ module ActionController
62
63
  extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
63
64
  extend ::ActionController::Railties::Helpers
64
65
 
66
+ wrap_parameters format: [:json] if options.wrap_parameters_by_default && respond_to?(:wrap_parameters)
67
+
65
68
  # Configs used in other initializers
66
- options = options.except(
69
+ filtered_options = options.except(
67
70
  :log_query_tags_around_actions,
68
71
  :permit_all_parameters,
69
72
  :action_on_unpermitted_parameters,
70
- :always_permitted_parameters
73
+ :always_permitted_parameters,
74
+ :wrap_parameters_by_default
71
75
  )
72
76
 
73
- options.each do |k, v|
77
+ filtered_options.each do |k, v|
74
78
  k = "#{k}="
75
79
  if respond_to?(k)
76
80
  send(k, v)
@@ -109,18 +113,20 @@ module ActionController
109
113
  if query_logs_tags_enabled
110
114
  app.config.active_record.query_log_tags += [:controller, :action]
111
115
 
112
- ActiveSupport.on_load(:action_controller) do
113
- include ActionController::QueryTags
114
- end
115
-
116
116
  ActiveSupport.on_load(:active_record) do
117
117
  ActiveRecord::QueryLogs.taggings.merge!(
118
- controller: ->(context) { context[:controller].controller_name },
119
- action: ->(context) { context[:controller].action_name },
120
- namespaced_controller: ->(context) { context[:controller].class.name }
118
+ controller: ->(context) { context[:controller]&.controller_name },
119
+ action: ->(context) { context[:controller]&.action_name },
120
+ namespaced_controller: ->(context) { context[:controller].class.name if context[:controller] }
121
121
  )
122
122
  end
123
123
  end
124
124
  end
125
+
126
+ initializer "action_controller.test_case" do |app|
127
+ ActiveSupport.on_load(:action_controller_test_case) do
128
+ ActionController::TestCase.executor_around_each_request = app.config.active_support.executor_around_test_case
129
+ end
130
+ end
125
131
  end
126
132
  end
@@ -333,6 +333,8 @@ module ActionController
333
333
  #
334
334
  # assert_redirected_to page_url(title: 'foo')
335
335
  class TestCase < ActiveSupport::TestCase
336
+ singleton_class.attr_accessor :executor_around_each_request
337
+
336
338
  module Behavior
337
339
  extend ActiveSupport::Concern
338
340
  include ActionDispatch::TestProcess
@@ -578,10 +580,19 @@ module ActionController
578
580
  end
579
581
  end
580
582
 
583
+ def wrap_execution(&block)
584
+ if ActionController::TestCase.executor_around_each_request && defined?(Rails.application) && Rails.application
585
+ Rails.application.executor.wrap(&block)
586
+ else
587
+ yield
588
+ end
589
+ end
590
+
581
591
  def process_controller_response(action, cookies, xhr)
582
592
  begin
583
593
  @controller.recycle!
584
- @controller.dispatch(action, @request, @response)
594
+
595
+ wrap_execution { @controller.dispatch(action, @request, @response) }
585
596
  ensure
586
597
  @request = @controller.request
587
598
  @response = @controller.response
@@ -627,7 +638,7 @@ module ActionController
627
638
  end
628
639
 
629
640
  def check_required_ivars
630
- # Sanity check for required instance variables so we can give an
641
+ # Check for required instance variables so we can give an
631
642
  # understandable error message.
632
643
  [:@routes, :@controller, :@request, :@response].each do |iv_name|
633
644
  if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
@@ -37,7 +37,6 @@ module ActionController
37
37
  autoload :Logging
38
38
  autoload :MimeResponds
39
39
  autoload :ParamsWrapper
40
- autoload :QueryTags
41
40
  autoload :Redirecting
42
41
  autoload :Renderers
43
42
  autoload :Rendering
@@ -86,18 +86,6 @@ module ActionDispatch # :nodoc:
86
86
  cattr_accessor :default_charset, default: "utf-8"
87
87
  cattr_accessor :default_headers
88
88
 
89
- def self.return_only_media_type_on_content_type=(*)
90
- ActiveSupport::Deprecation.warn(
91
- ".return_only_media_type_on_content_type= is deprecated with no replacement and will be removed in 7.0."
92
- )
93
- end
94
-
95
- def self.return_only_media_type_on_content_type
96
- ActiveSupport::Deprecation.warn(
97
- ".return_only_media_type_on_content_type is deprecated with no replacement and will be removed in 7.0."
98
- )
99
- end
100
-
101
89
  include Rack::Response::Helpers
102
90
  # Aliasing these off because AD::Http::Cache::Response defines them.
103
91
  alias :_cache_control :cache_control
@@ -71,7 +71,8 @@ module ActionDispatch
71
71
  path = options[:script_name].to_s.chomp("/")
72
72
  path << options[:path] if options.key?(:path)
73
73
 
74
- add_trailing_slash(path) if options[:trailing_slash]
74
+ path = "/" if options[:trailing_slash] && path.blank?
75
+
75
76
  add_params(path, options[:params]) if options.key?(:params)
76
77
  add_anchor(path, options[:anchor]) if options.key?(:anchor)
77
78
 
@@ -101,14 +102,6 @@ module ActionDispatch
101
102
  parts[0..-(tld_length + 2)]
102
103
  end
103
104
 
104
- def add_trailing_slash(path)
105
- if path.include?("?")
106
- path.sub!(/\?/, '/\&')
107
- elsif !path.include?(".")
108
- path.sub!(/[^\/]\z|\A\z/, '\&/')
109
- end
110
- end
111
-
112
105
  def build_host_url(host, port, protocol, options, path)
113
106
  if match = host.match(HOST_REGEXP)
114
107
  protocol ||= match[1] unless protocol == false
@@ -53,7 +53,7 @@ module ActionDispatch
53
53
  if formatted != false
54
54
  # Add a constraint for wildcard route to make it non-greedy and
55
55
  # match the optional format part of the route by default.
56
- wildcard_options[node.name.to_sym] ||= /.+?/
56
+ wildcard_options[node.name.to_sym] ||= /.+?/m
57
57
  end
58
58
  end
59
59
 
@@ -166,7 +166,7 @@ module ActionDispatch
166
166
  super(left)
167
167
 
168
168
  # By default wildcard routes are non-greedy and must match something.
169
- @regexp = /.+?/
169
+ @regexp = /.+?/m
170
170
  end
171
171
 
172
172
  def star?; true; end
@@ -91,7 +91,7 @@ module ActionDispatch
91
91
  # as requirements.
92
92
  def requirements
93
93
  @defaults.merge(path.requirements).delete_if { |_, v|
94
- /.+?/ == v
94
+ /.+?/m == v
95
95
  }
96
96
  end
97
97
 
@@ -439,7 +439,7 @@ module ActionDispatch
439
439
  end
440
440
 
441
441
  def write_cookie?(cookie)
442
- request.ssl? || !cookie[:secure] || always_write_cookie
442
+ request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
443
443
  end
444
444
 
445
445
  def handle_options(options)
@@ -13,6 +13,9 @@ module ActionDispatch
13
13
  begin
14
14
  response = @app.call(env)
15
15
  returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
16
+ rescue => error
17
+ @executor.error_reporter.report(error, handled: false)
18
+ raise
16
19
  ensure
17
20
  state.complete! unless returned
18
21
  end
@@ -11,7 +11,10 @@ module ActionDispatch
11
11
  #
12
12
  # When a request comes to an unauthorized host, the +response_app+
13
13
  # application will be executed and rendered. If no +response_app+ is given, a
14
- # default one will run, which responds with <tt>403 Forbidden</tt>.
14
+ # default one will run.
15
+ # The default response app logs blocked host info with level 'error' and
16
+ # responds with <tt>403 Forbidden</tt>. The body of the response contains debug info
17
+ # if +config.consider_all_requests_local+ is set to true, otherwise the body is empty.
15
18
  class HostAuthorization
16
19
  class Permissions # :nodoc:
17
20
  def initialize(hosts)
@@ -56,34 +59,51 @@ module ActionDispatch
56
59
  end
57
60
  end
58
61
 
59
- DEFAULT_RESPONSE_APP = -> env do
60
- request = Request.new(env)
62
+ class DefaultResponseApp # :nodoc:
63
+ RESPONSE_STATUS = 403
64
+
65
+ def call(env)
66
+ request = Request.new(env)
67
+ format = request.xhr? ? "text/plain" : "text/html"
61
68
 
62
- format = request.xhr? ? "text/plain" : "text/html"
63
- template = DebugView.new(host: request.host)
64
- body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
69
+ log_error(request)
70
+ response(format, response_body(request))
71
+ end
72
+
73
+ private
74
+ def response_body(request)
75
+ return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
76
+
77
+ template = DebugView.new(host: request.host)
78
+ template.render(template: "rescues/blocked_host", layout: "rescues/layout")
79
+ end
80
+
81
+ def response(format, body)
82
+ [RESPONSE_STATUS,
83
+ { "Content-Type" => "#{format}; charset=#{Response.default_charset}",
84
+ "Content-Length" => body.bytesize.to_s },
85
+ [body]]
86
+ end
65
87
 
66
- [403, {
67
- "Content-Type" => "#{format}; charset=#{Response.default_charset}",
68
- "Content-Length" => body.bytesize.to_s,
69
- }, [body]]
88
+ def log_error(request)
89
+ logger = available_logger(request)
90
+
91
+ return unless logger
92
+
93
+ logger.error("[#{self.class.name}] Blocked host: #{request.host}")
94
+ end
95
+
96
+ def available_logger(request)
97
+ request.logger || ActionView::Base.logger
98
+ end
70
99
  end
71
100
 
72
- def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
101
+ def initialize(app, hosts, exclude: nil, response_app: nil)
73
102
  @app = app
74
103
  @permissions = Permissions.new(hosts)
75
104
  @exclude = exclude
76
105
 
77
- unless deprecated_response_app.nil?
78
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
79
- `action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 7.0.
80
- Use the Host Authorization `response_app` setting instead.
81
- MSG
82
-
83
- response_app ||= deprecated_response_app
84
- end
85
-
86
- @response_app = response_app || DEFAULT_RESPONSE_APP
106
+ @response_app = response_app || DefaultResponseApp.new
87
107
  end
88
108
 
89
109
  def call(env)
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/notifications"
4
+
5
+ module ActionDispatch
6
+ class ServerTiming
7
+ SERVER_TIMING_HEADER = "Server-Timing"
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ events = []
15
+ subscriber = ActiveSupport::Notifications.subscribe(/.*/) do |*args|
16
+ events << ActiveSupport::Notifications::Event.new(*args)
17
+ end
18
+
19
+ status, headers, body = begin
20
+ @app.call(env)
21
+ ensure
22
+ ActiveSupport::Notifications.unsubscribe(subscriber)
23
+ end
24
+
25
+ header_info = events.group_by(&:name).map do |event_name, events_collection|
26
+ "#{event_name};dur=#{events_collection.sum(&:duration)}"
27
+ end
28
+ headers[SERVER_TIMING_HEADER] = header_info.join(", ")
29
+
30
+ [ status, headers, body ]
31
+ end
32
+ end
33
+ end
@@ -40,6 +40,7 @@ module ActionDispatch
40
40
  request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
41
41
  request.set_header "action_dispatch.original_path", request.path_info
42
42
  request.set_header "action_dispatch.original_request_method", request.raw_request_method
43
+ fallback_to_html_format_if_invalid_mime_type(request)
43
44
  request.path_info = "/#{status}"
44
45
  request.request_method = "GET"
45
46
  response = @exceptions_app.call(request.env)
@@ -54,6 +55,15 @@ module ActionDispatch
54
55
  "went wrong."]]
55
56
  end
56
57
 
58
+ def fallback_to_html_format_if_invalid_mime_type(request)
59
+ # If the MIME type for the request is invalid then the
60
+ # @exceptions_app may not be able to handle it. To make it
61
+ # easier to handle, we switch to HTML.
62
+ request.formats
63
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
64
+ request.set_header "HTTP_ACCEPT", "text/html"
65
+ end
66
+
57
67
  def pass_response(status)
58
68
  [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []]
59
69
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rack/utils"
4
- require "active_support/core_ext/uri"
5
4
 
6
5
  module ActionDispatch
7
6
  # This middleware serves static files from disk, if available.
@@ -4,4 +4,5 @@
4
4
  <main role="main" id="container">
5
5
  <h2>To allow requests to <%= @host %> make sure it is a valid hostname (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:</h2>
6
6
  <pre>config.hosts &lt;&lt; "<%= @host %>"</pre>
7
+ <p>For more details view: <a href="https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization">the Host Authorization guide</a></p>
7
8
  </main>
@@ -3,3 +3,5 @@ Blocked host: <%= @host %>
3
3
  To allow requests to <%= @host %> make sure it is a valid hostname (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:
4
4
 
5
5
  config.hosts << "<%= @host %>"
6
+
7
+ For more details on host authorization view: https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization
@@ -5,7 +5,7 @@ require "io/console/size"
5
5
 
6
6
  module ActionDispatch
7
7
  module Routing
8
- class RouteWrapper < SimpleDelegator
8
+ class RouteWrapper < SimpleDelegator # :nodoc:
9
9
  def endpoint
10
10
  app.dispatcher? ? "#{controller}##{action}" : rack_app.inspect
11
11
  end
@@ -146,7 +146,7 @@ module ActionDispatch
146
146
  end
147
147
 
148
148
  requirements, conditions = split_constraints ast.path_params, constraints
149
- verify_regexp_requirements requirements.map(&:last).grep(Regexp)
149
+ verify_regexp_requirements requirements, ast.wildcard_options
150
150
 
151
151
  formats = normalize_format(formatted)
152
152
 
@@ -246,14 +246,18 @@ module ActionDispatch
246
246
  end
247
247
  end
248
248
 
249
- def verify_regexp_requirements(requirements)
250
- requirements.each do |requirement|
251
- if ANCHOR_CHARACTERS_REGEX.match?(requirement.source)
249
+ def verify_regexp_requirements(requirements, wildcard_options)
250
+ requirements.each do |requirement, regex|
251
+ next unless regex.is_a? Regexp
252
+
253
+ if ANCHOR_CHARACTERS_REGEX.match?(regex.source)
252
254
  raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
253
255
  end
254
256
 
255
- if requirement.multiline?
256
- raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
257
+ if regex.multiline?
258
+ next if wildcard_options.key?(requirement)
259
+
260
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{regex.inspect}"
257
261
  end
258
262
  end
259
263
  end
@@ -820,6 +820,11 @@ module ActionDispatch
820
820
 
821
821
  route_with_params = generate(route_name, path_options, recall)
822
822
  path = route_with_params.path(method_name)
823
+
824
+ if options[:trailing_slash] && !options[:format] && !path.end_with?("/")
825
+ path += "/"
826
+ end
827
+
823
828
  params = route_with_params.params
824
829
 
825
830
  if options.key? :params
@@ -115,6 +115,8 @@ module ActionDispatch
115
115
  include SystemTesting::TestHelpers::SetupAndTeardown
116
116
  include SystemTesting::TestHelpers::ScreenshotHelper
117
117
 
118
+ DEFAULT_HOST = "http://127.0.0.1"
119
+
118
120
  def initialize(*) # :nodoc:
119
121
  super
120
122
  self.class.driven_by(:selenium) unless self.class.driver?
@@ -166,7 +168,11 @@ module ActionDispatch
166
168
  include ActionDispatch.test_app.routes.mounted_helpers
167
169
 
168
170
  def url_options
169
- default_url_options.reverse_merge(host: Capybara.app_host || Capybara.current_session.server_url)
171
+ default_url_options.reverse_merge(host: app_host)
172
+ end
173
+
174
+ def app_host
175
+ Capybara.app_host || Capybara.current_session.server_url || DEFAULT_HOST
170
176
  end
171
177
  end.new
172
178
  end
@@ -33,19 +33,9 @@ module ActionDispatch
33
33
  def preload
34
34
  case type
35
35
  when :chrome
36
- if ::Selenium::WebDriver::Service.respond_to? :driver_path=
37
- ::Selenium::WebDriver::Chrome::Service.driver_path&.call
38
- else
39
- # Selenium <= v3.141.0
40
- ::Selenium::WebDriver::Chrome.driver_path
41
- end
36
+ ::Selenium::WebDriver::Chrome::Service.driver_path&.call
42
37
  when :firefox
43
- if ::Selenium::WebDriver::Service.respond_to? :driver_path=
44
- ::Selenium::WebDriver::Firefox::Service.driver_path&.call
45
- else
46
- # Selenium <= v3.141.0
47
- ::Selenium::WebDriver::Firefox.driver_path
48
- end
38
+ ::Selenium::WebDriver::Firefox::Service.driver_path&.call
49
39
  end
50
40
  end
51
41