actionpack 5.2.4.4 → 6.1.1

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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -322
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller.rb +1 -0
  6. data/lib/abstract_controller/base.rb +38 -4
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/caching/fragments.rb +6 -22
  9. data/lib/abstract_controller/callbacks.rb +14 -2
  10. data/lib/abstract_controller/collector.rb +1 -2
  11. data/lib/abstract_controller/helpers.rb +106 -90
  12. data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
  13. data/lib/abstract_controller/rendering.rb +9 -9
  14. data/lib/abstract_controller/translation.rb +11 -5
  15. data/lib/action_controller.rb +7 -4
  16. data/lib/action_controller/api.rb +4 -3
  17. data/lib/action_controller/base.rb +6 -9
  18. data/lib/action_controller/caching.rb +1 -3
  19. data/lib/action_controller/log_subscriber.rb +10 -7
  20. data/lib/action_controller/metal.rb +10 -8
  21. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  22. data/lib/action_controller/metal/conditional_get.rb +19 -5
  23. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  24. data/lib/action_controller/metal/cookies.rb +3 -1
  25. data/lib/action_controller/metal/data_streaming.rb +6 -7
  26. data/lib/action_controller/metal/default_headers.rb +17 -0
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
  28. data/lib/action_controller/metal/exceptions.rb +56 -2
  29. data/lib/action_controller/metal/flash.rb +5 -5
  30. data/lib/action_controller/metal/head.rb +7 -4
  31. data/lib/action_controller/metal/helpers.rb +14 -5
  32. data/lib/action_controller/metal/http_authentication.rb +24 -23
  33. data/lib/action_controller/metal/implicit_render.rb +5 -15
  34. data/lib/action_controller/metal/instrumentation.rb +13 -14
  35. data/lib/action_controller/metal/live.rb +30 -32
  36. data/lib/action_controller/metal/logging.rb +20 -0
  37. data/lib/action_controller/metal/mime_responds.rb +19 -4
  38. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  39. data/lib/action_controller/metal/params_wrapper.rb +31 -22
  40. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  41. data/lib/action_controller/metal/redirecting.rb +6 -6
  42. data/lib/action_controller/metal/renderers.rb +4 -4
  43. data/lib/action_controller/metal/rendering.rb +8 -3
  44. data/lib/action_controller/metal/request_forgery_protection.rb +62 -34
  45. data/lib/action_controller/metal/rescue.rb +1 -1
  46. data/lib/action_controller/metal/streaming.rb +0 -1
  47. data/lib/action_controller/metal/strong_parameters.rb +167 -58
  48. data/lib/action_controller/metal/url_for.rb +1 -1
  49. data/lib/action_controller/railties/helpers.rb +1 -1
  50. data/lib/action_controller/renderer.rb +37 -13
  51. data/lib/action_controller/template_assertions.rb +1 -1
  52. data/lib/action_controller/test_case.rb +70 -65
  53. data/lib/action_dispatch.rb +9 -3
  54. data/lib/action_dispatch/http/cache.rb +26 -21
  55. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  56. data/lib/action_dispatch/http/content_security_policy.rb +33 -19
  57. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  58. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  59. data/lib/action_dispatch/http/headers.rb +4 -4
  60. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  61. data/lib/action_dispatch/http/mime_type.rb +42 -23
  62. data/lib/action_dispatch/http/parameters.rb +14 -23
  63. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  64. data/lib/action_dispatch/http/request.rb +45 -22
  65. data/lib/action_dispatch/http/response.rb +45 -25
  66. data/lib/action_dispatch/http/upload.rb +9 -1
  67. data/lib/action_dispatch/http/url.rb +82 -82
  68. data/lib/action_dispatch/journey.rb +0 -2
  69. data/lib/action_dispatch/journey/formatter.rb +54 -30
  70. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  71. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  72. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  73. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  74. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  75. data/lib/action_dispatch/journey/parser.rb +13 -13
  76. data/lib/action_dispatch/journey/parser.y +1 -1
  77. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  78. data/lib/action_dispatch/journey/route.rb +10 -20
  79. data/lib/action_dispatch/journey/router.rb +26 -34
  80. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  81. data/lib/action_dispatch/journey/routes.rb +0 -2
  82. data/lib/action_dispatch/journey/scanner.rb +10 -4
  83. data/lib/action_dispatch/journey/visitors.rb +1 -4
  84. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  85. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  86. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  87. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  88. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  89. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  90. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  91. data/lib/action_dispatch/middleware/flash.rb +1 -1
  92. data/lib/action_dispatch/middleware/host_authorization.rb +121 -0
  93. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  94. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  95. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  96. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  97. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  98. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  99. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  100. data/lib/action_dispatch/middleware/stack.rb +56 -2
  101. data/lib/action_dispatch/middleware/static.rb +153 -93
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  107. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  108. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  114. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -1
  115. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  123. data/lib/action_dispatch/railtie.rb +8 -2
  124. data/lib/action_dispatch/request/session.rb +10 -9
  125. data/lib/action_dispatch/request/utils.rb +26 -2
  126. data/lib/action_dispatch/routing.rb +21 -20
  127. data/lib/action_dispatch/routing/inspector.rb +100 -52
  128. data/lib/action_dispatch/routing/mapper.rb +155 -103
  129. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  130. data/lib/action_dispatch/routing/redirection.rb +3 -3
  131. data/lib/action_dispatch/routing/route_set.rb +71 -69
  132. data/lib/action_dispatch/routing/url_for.rb +2 -2
  133. data/lib/action_dispatch/system_test_case.rb +54 -11
  134. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  135. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  136. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  137. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  138. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  139. data/lib/action_dispatch/testing/assertions.rb +1 -1
  140. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  141. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  142. data/lib/action_dispatch/testing/integration.rb +61 -28
  143. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  144. data/lib/action_dispatch/testing/test_process.rb +29 -4
  145. data/lib/action_dispatch/testing/test_request.rb +3 -3
  146. data/lib/action_dispatch/testing/test_response.rb +4 -32
  147. data/lib/action_pack.rb +1 -1
  148. data/lib/action_pack/gem_version.rb +4 -4
  149. metadata +38 -26
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -4,6 +4,7 @@ require "active_support/core_ext/hash/slice"
4
4
  require "active_support/core_ext/enumerable"
5
5
  require "active_support/core_ext/array/extract_options"
6
6
  require "active_support/core_ext/regexp"
7
+ require "active_support/core_ext/symbol/starts_ends_with"
7
8
  require "action_dispatch/routing/redirection"
8
9
  require "action_dispatch/routing/endpoint"
9
10
 
@@ -50,7 +51,19 @@ module ActionDispatch
50
51
 
51
52
  private
52
53
  def constraint_args(constraint, request)
53
- constraint.arity == 1 ? [request] : [request.path_parameters, request]
54
+ arity = if constraint.respond_to?(:arity)
55
+ constraint.arity
56
+ else
57
+ constraint.method(:call).arity
58
+ end
59
+
60
+ if arity < 1
61
+ []
62
+ elsif arity == 1
63
+ [request]
64
+ else
65
+ [request.path_parameters, request]
66
+ end
54
67
  end
55
68
  end
56
69
 
@@ -58,17 +71,21 @@ module ActionDispatch
58
71
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
59
72
  OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
60
73
 
61
- attr_reader :requirements, :defaults
62
- attr_reader :to, :default_controller, :default_action
63
- attr_reader :required_defaults, :ast
74
+ attr_reader :requirements, :defaults, :to, :default_controller,
75
+ :default_action, :required_defaults, :ast, :scope_options
64
76
 
65
77
  def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
66
- options = scope[:options].merge(options) if scope[:options]
67
-
68
- defaults = (scope[:defaults] || {}).dup
69
- scope_constraints = scope[:constraints] || {}
78
+ scope_params = {
79
+ blocks: scope[:blocks] || [],
80
+ constraints: scope[:constraints] || {},
81
+ defaults: (scope[:defaults] || {}).dup,
82
+ module: scope[:module],
83
+ options: scope[:options] || {}
84
+ }
70
85
 
71
- new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
86
+ new set: set, ast: ast, controller: controller, default_action: default_action,
87
+ to: to, formatted: formatted, via: via, options_constraints: options_constraints,
88
+ anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options)
72
89
  end
73
90
 
74
91
  def self.check_via(via)
@@ -96,36 +113,48 @@ module ActionDispatch
96
113
  end
97
114
 
98
115
  def self.optional_format?(path, format)
99
- format != false && path !~ OPTIONAL_FORMAT_REGEX
116
+ format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
100
117
  end
101
118
 
102
- def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
103
- @defaults = defaults
104
- @set = set
105
-
106
- @to = to
107
- @default_controller = controller
108
- @default_action = default_action
119
+ def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:)
120
+ @defaults = scope_params[:defaults]
121
+ @set = set
122
+ @to = intern(to)
123
+ @default_controller = intern(controller)
124
+ @default_action = intern(default_action)
109
125
  @ast = ast
110
126
  @anchor = anchor
111
127
  @via = via
112
128
  @internal = options.delete(:internal)
129
+ @scope_options = scope_params[:options]
130
+
131
+ path_params = []
132
+ wildcard_options = {}
133
+ ast.each do |node|
134
+ if node.symbol?
135
+ path_params << node.to_sym
136
+ elsif formatted != false && node.star?
137
+ # Add a constraint for wildcard route to make it non-greedy and match the
138
+ # optional format part of the route by default.
139
+ wildcard_options[node.name.to_sym] ||= /.+?/
140
+ elsif node.cat?
141
+ alter_regex_for_custom_routes(node)
142
+ end
143
+ end
113
144
 
114
- path_params = ast.find_all(&:symbol?).map(&:to_sym)
115
-
116
- options = add_wildcard_options(options, formatted, ast)
145
+ options = wildcard_options.merge!(options)
117
146
 
118
- options = normalize_options!(options, path_params, modyoule)
147
+ options = normalize_options!(options, path_params, scope_params[:module])
119
148
 
120
149
  split_options = constraints(options, path_params)
121
150
 
122
- constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
151
+ constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
123
152
 
124
153
  if options_constraints.is_a?(Hash)
125
154
  @defaults = Hash[options_constraints.find_all { |key, default|
126
155
  URL_OPTIONS.include?(key) && (String === default || Integer === default)
127
156
  }].merge @defaults
128
- @blocks = blocks
157
+ @blocks = scope_params[:blocks]
129
158
  constraints.merge! options_constraints
130
159
  else
131
160
  @blocks = blocks(options_constraints)
@@ -148,25 +177,20 @@ module ActionDispatch
148
177
  end
149
178
 
150
179
  def make_route(name, precedence)
151
- route = Journey::Route.new(name,
152
- application,
153
- path,
154
- conditions,
155
- required_defaults,
156
- defaults,
157
- request_method,
158
- precedence,
159
- @internal)
160
-
161
- route
180
+ Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
181
+ required_defaults: required_defaults, defaults: defaults,
182
+ request_method_match: request_method, precedence: precedence,
183
+ scope_options: scope_options, internal: @internal)
162
184
  end
163
185
 
164
186
  def application
165
187
  app(@blocks)
166
188
  end
167
189
 
190
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
191
+
168
192
  def path
169
- build_path @ast, requirements, @anchor
193
+ Journey::Path::Pattern.new(@ast, requirements, JOINED_SEPARATORS, @anchor)
170
194
  end
171
195
 
172
196
  def conditions
@@ -187,16 +211,10 @@ module ActionDispatch
187
211
  end
188
212
  private :request_method
189
213
 
190
- JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
191
-
192
- def build_path(ast, requirements, anchor)
193
- pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
194
-
214
+ private
195
215
  # Find all the symbol nodes that are adjacent to literal nodes and alter
196
216
  # the regexp so that Journey will partition them into custom routes.
197
- ast.find_all { |node|
198
- next unless node.cat?
199
-
217
+ def alter_regex_for_custom_routes(node)
200
218
  if node.left.literal? && node.right.symbol?
201
219
  symbol = node.right
202
220
  elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
@@ -205,30 +223,15 @@ module ActionDispatch
205
223
  symbol = node.left
206
224
  elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
207
225
  symbol = node.left
208
- else
209
- next
210
226
  end
211
227
 
212
228
  if symbol
213
229
  symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
214
230
  end
215
- }
216
-
217
- pattern
218
- end
219
- private :build_path
231
+ end
220
232
 
221
- private
222
- def add_wildcard_options(options, formatted, path_ast)
223
- # Add a constraint for wildcard route to make it non-greedy and match the
224
- # optional format part of the route by default.
225
- if formatted != false
226
- path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
227
- hash[node.name.to_sym] ||= /.+?/
228
- }.merge options
229
- else
230
- options
231
- end
233
+ def intern(object)
234
+ object.is_a?(String) ? -object : object
232
235
  end
233
236
 
234
237
  def normalize_options!(options, path_params, modyoule)
@@ -279,7 +282,7 @@ module ActionDispatch
279
282
 
280
283
  def verify_regexp_requirements(requirements)
281
284
  requirements.each do |requirement|
282
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
285
+ if ANCHOR_CHARACTERS_REGEX.match?(requirement.source)
283
286
  raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
284
287
  end
285
288
 
@@ -308,8 +311,8 @@ module ActionDispatch
308
311
  def check_controller_and_action(path_params, controller, action)
309
312
  hash = check_part(:controller, controller, path_params, {}) do |part|
310
313
  translate_controller(part) {
311
- message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup
312
- message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
314
+ message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems."
315
+ message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
313
316
 
314
317
  raise ArgumentError, message
315
318
  }
@@ -333,8 +336,8 @@ module ActionDispatch
333
336
  end
334
337
 
335
338
  def split_to(to)
336
- if to =~ /#/
337
- to.split("#")
339
+ if /#/.match?(to)
340
+ to.split("#").map!(&:-@)
338
341
  else
339
342
  []
340
343
  end
@@ -342,10 +345,10 @@ module ActionDispatch
342
345
 
343
346
  def add_controller_module(controller, modyoule)
344
347
  if modyoule && !controller.is_a?(Regexp)
345
- if controller =~ %r{\A/}
346
- controller[1..-1]
348
+ if controller&.start_with?("/")
349
+ -controller[1..-1]
347
350
  else
348
- [modyoule, controller].compact.join("/")
351
+ -[modyoule, controller].compact.join("/")
349
352
  end
350
353
  else
351
354
  controller
@@ -354,7 +357,7 @@ module ActionDispatch
354
357
 
355
358
  def translate_controller(controller)
356
359
  return controller if Regexp === controller
357
- return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
360
+ return controller.to_s if /\A[a-z_0-9][a-z_0-9\/]*\z/.match?(controller)
358
361
 
359
362
  yield
360
363
  end
@@ -385,12 +388,23 @@ module ActionDispatch
385
388
  end
386
389
  end
387
390
 
388
- # Invokes Journey::Router::Utils.normalize_path and ensure that
389
- # (:locale) becomes (/:locale) instead of /(:locale). Except
390
- # for root cases, where the latter is the correct one.
391
+ # Invokes Journey::Router::Utils.normalize_path, then ensures that
392
+ # /(:locale) becomes (/:locale). Except for root cases, where the
393
+ # former is the correct one.
391
394
  def self.normalize_path(path)
392
395
  path = Journey::Router::Utils.normalize_path(path)
393
- path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/(\(+[^)]+\)){1,}$}
396
+
397
+ # the path for a root URL at this point can be something like
398
+ # "/(/:locale)(/:platform)/(:browser)", and we would want
399
+ # "/(:locale)(/:platform)(/:browser)"
400
+
401
+ # reverse "/(", "/((" etc to "(/", "((/" etc
402
+ path.gsub!(%r{/(\(+)/?}, '\1/')
403
+ # if a path is all optional segments, change the leading "(/" back to
404
+ # "/(" so it evaluates to "/" when interpreted with no options.
405
+ # Unless, however, at least one secondary segment consists of a static
406
+ # part, ex. "(/:locale)(/pages/:page)"
407
+ path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path)
394
408
  path
395
409
  end
396
410
 
@@ -547,16 +561,16 @@ module ActionDispatch
547
561
  # Constrains parameters with a hash of regular expressions
548
562
  # or an object that responds to <tt>matches?</tt>. In addition, constraints
549
563
  # other than path can also be specified with any object
550
- # that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
564
+ # that responds to <tt>===</tt> (e.g. String, Array, Range, etc.).
551
565
  #
552
566
  # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
553
567
  #
554
568
  # match 'json_only', constraints: { format: 'json' }, via: :get
555
569
  #
556
- # class Whitelist
570
+ # class PermitList
557
571
  # def matches?(request) request.remote_ip == '1.2.3.4' end
558
572
  # end
559
- # match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
573
+ # match 'path', to: 'c#a', constraints: PermitList.new, via: :get
560
574
  #
561
575
  # See <tt>Scoping#constraints</tt> for more examples with its scope
562
576
  # equivalent.
@@ -611,7 +625,7 @@ module ActionDispatch
611
625
  end
612
626
 
613
627
  raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
614
- raise ArgumentError, <<-MSG.strip_heredoc unless path
628
+ raise ArgumentError, <<~MSG unless path
615
629
  Must be called with mount point
616
630
 
617
631
  mount SomeRackApp, at: "some_route"
@@ -644,7 +658,7 @@ module ActionDispatch
644
658
 
645
659
  # Query if the following named route was already defined.
646
660
  def has_named_route?(name)
647
- @set.named_routes.key? name
661
+ @set.named_routes.key?(name)
648
662
  end
649
663
 
650
664
  private
@@ -668,7 +682,7 @@ module ActionDispatch
668
682
 
669
683
  script_namer = ->(options) do
670
684
  prefix_options = options.slice(*_route.segment_keys)
671
- prefix_options[:relative_url_root] = "".freeze
685
+ prefix_options[:relative_url_root] = ""
672
686
 
673
687
  if options[:_recall]
674
688
  prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
@@ -676,7 +690,7 @@ module ActionDispatch
676
690
 
677
691
  # We must actually delete prefix segment keys to avoid passing them to next url_for.
678
692
  _route.segment_keys.each { |k| options.delete(k) }
679
- _url_helpers.send("#{name}_path", prefix_options)
693
+ _url_helpers.public_send("#{name}_path", prefix_options)
680
694
  end
681
695
 
682
696
  app.routes.define_mounted_helper(name, script_namer)
@@ -736,6 +750,14 @@ module ActionDispatch
736
750
  map_method(:delete, args, &block)
737
751
  end
738
752
 
753
+ # Define a route that only recognizes HTTP OPTIONS.
754
+ # For supported arguments, see match[rdoc-ref:Base#match]
755
+ #
756
+ # options 'carrots', to: 'food#carrots'
757
+ def options(*args, &block)
758
+ map_method(:options, args, &block)
759
+ end
760
+
739
761
  private
740
762
  def map_method(method, args, &block)
741
763
  options = args.extract_options!
@@ -983,7 +1005,7 @@ module ActionDispatch
983
1005
  #
984
1006
  # Requests to routes can be constrained based on specific criteria:
985
1007
  #
986
- # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
1008
+ # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
987
1009
  # resources :iphones
988
1010
  # end
989
1011
  #
@@ -993,7 +1015,7 @@ module ActionDispatch
993
1015
  #
994
1016
  # class Iphone
995
1017
  # def self.matches?(request)
996
- # request.env["HTTP_USER_AGENT"] =~ /iPhone/
1018
+ # /iPhone/.match?(request.env["HTTP_USER_AGENT"])
997
1019
  # end
998
1020
  # end
999
1021
  #
@@ -1138,6 +1160,10 @@ module ActionDispatch
1138
1160
  attr_reader :controller, :path, :param
1139
1161
 
1140
1162
  def initialize(entities, api_only, shallow, options = {})
1163
+ if options[:param].to_s.include?(":")
1164
+ raise ArgumentError, ":param option can't contain colons"
1165
+ end
1166
+
1141
1167
  @name = entities.to_s
1142
1168
  @path = (options[:path] || @name).to_s
1143
1169
  @controller = (options[:controller] || @name).to_s
@@ -1159,10 +1185,16 @@ module ActionDispatch
1159
1185
  end
1160
1186
 
1161
1187
  def actions
1188
+ if @except
1189
+ available_actions - Array(@except).map(&:to_sym)
1190
+ else
1191
+ available_actions
1192
+ end
1193
+ end
1194
+
1195
+ def available_actions
1162
1196
  if @only
1163
1197
  Array(@only).map(&:to_sym)
1164
- elsif @except
1165
- default_actions - Array(@except).map(&:to_sym)
1166
1198
  else
1167
1199
  default_actions
1168
1200
  end
@@ -1389,6 +1421,8 @@ module ActionDispatch
1389
1421
  # as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
1390
1422
  # to be shortened to just <tt>/comments/1234</tt>.
1391
1423
  #
1424
+ # Set <tt>shallow: false</tt> on a child resource to ignore a parent's shallow parameter.
1425
+ #
1392
1426
  # [:shallow_path]
1393
1427
  # Prefixes nested shallow routes with the specified path.
1394
1428
  #
@@ -1431,6 +1465,9 @@ module ActionDispatch
1431
1465
  # Allows you to specify the default value for optional +format+
1432
1466
  # segment or disable it by supplying +false+.
1433
1467
  #
1468
+ # [:param]
1469
+ # Allows you to override the default param name of +:id+ in the URL.
1470
+ #
1434
1471
  # === Examples
1435
1472
  #
1436
1473
  # # routes call <tt>Admin::PostsController</tt>
@@ -1571,6 +1608,22 @@ module ActionDispatch
1571
1608
  !parent_resource.singleton? && @scope[:shallow]
1572
1609
  end
1573
1610
 
1611
+ def draw(name)
1612
+ path = @draw_paths.find do |_path|
1613
+ File.exist? "#{_path}/#{name}.rb"
1614
+ end
1615
+
1616
+ unless path
1617
+ msg = "Your router tried to #draw the external file #{name}.rb,\n" \
1618
+ "but the file was not found in:\n\n"
1619
+ msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
1620
+ raise ArgumentError, msg
1621
+ end
1622
+
1623
+ route_path = "#{path}/#{name}.rb"
1624
+ instance_eval(File.read(route_path), route_path.to_s)
1625
+ end
1626
+
1574
1627
  # Matches a URL pattern to one or more routes.
1575
1628
  # For more information, see match[rdoc-ref:Base#match].
1576
1629
  #
@@ -1588,7 +1641,7 @@ module ActionDispatch
1588
1641
  when Symbol
1589
1642
  options[:action] = to
1590
1643
  when String
1591
- if to =~ /#/
1644
+ if /#/.match?(to)
1592
1645
  options[:to] = to
1593
1646
  else
1594
1647
  options[:controller] = to
@@ -1645,26 +1698,26 @@ module ActionDispatch
1645
1698
  end
1646
1699
 
1647
1700
  private
1648
-
1649
1701
  def parent_resource
1650
1702
  @scope[:scope_level_resource]
1651
1703
  end
1652
1704
 
1653
1705
  def apply_common_behavior_for(method, resources, options, &block)
1654
1706
  if resources.length > 1
1655
- resources.each { |r| send(method, r, options, &block) }
1707
+ resources.each { |r| public_send(method, r, options, &block) }
1656
1708
  return true
1657
1709
  end
1658
1710
 
1659
- if options.delete(:shallow)
1711
+ if options[:shallow]
1712
+ options.delete(:shallow)
1660
1713
  shallow do
1661
- send(method, resources.pop, options, &block)
1714
+ public_send(method, resources.pop, options, &block)
1662
1715
  end
1663
1716
  return true
1664
1717
  end
1665
1718
 
1666
1719
  if resource_scope?
1667
- nested { send(method, resources.pop, options, &block) }
1720
+ nested { public_send(method, resources.pop, options, &block) }
1668
1721
  return true
1669
1722
  end
1670
1723
 
@@ -1675,7 +1728,7 @@ module ActionDispatch
1675
1728
  scope_options = options.slice!(*RESOURCE_OPTIONS)
1676
1729
  unless scope_options.empty?
1677
1730
  scope(scope_options) do
1678
- send(method, resources.pop, options, &block)
1731
+ public_send(method, resources.pop, options, &block)
1679
1732
  end
1680
1733
  return true
1681
1734
  end
@@ -1805,7 +1858,7 @@ module ActionDispatch
1805
1858
  # and return nil in case it isn't. Otherwise, we pass the invalid name
1806
1859
  # forward so the underlying router engine treats it and raises an exception.
1807
1860
  if as.nil?
1808
- candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
1861
+ candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
1809
1862
  else
1810
1863
  candidate
1811
1864
  end
@@ -1859,7 +1912,7 @@ module ActionDispatch
1859
1912
  options_constraints = options.delete(:constraints) || {}
1860
1913
 
1861
1914
  path_types = paths.group_by(&:class)
1862
- path_types.fetch(String, []).each do |_path|
1915
+ (path_types[String] || []).each do |_path|
1863
1916
  route_options = options.dup
1864
1917
  if _path && option_path
1865
1918
  raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
@@ -1868,7 +1921,7 @@ module ActionDispatch
1868
1921
  decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1869
1922
  end
1870
1923
 
1871
- path_types.fetch(Symbol, []).each do |action|
1924
+ (path_types[Symbol] || []).each do |action|
1872
1925
  route_options = options.dup
1873
1926
  decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
1874
1927
  end
@@ -1881,14 +1934,14 @@ module ActionDispatch
1881
1934
 
1882
1935
  path_without_format = path.sub(/\(\.:format\)$/, "")
1883
1936
  if using_match_shorthand?(path_without_format)
1884
- path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1937
+ path_without_format.delete_prefix("/").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1885
1938
  else
1886
1939
  nil
1887
1940
  end
1888
1941
  end
1889
1942
 
1890
1943
  def using_match_shorthand?(path)
1891
- path =~ %r{^/?[-\w]+/[-\w/]+$}
1944
+ %r{^/?[-\w]+/[-\w/]+$}.match?(path)
1892
1945
  end
1893
1946
 
1894
1947
  def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
@@ -1914,7 +1967,7 @@ module ActionDispatch
1914
1967
 
1915
1968
  default_action = options.delete(:action) || @scope[:action]
1916
1969
 
1917
- if action =~ /^[\w\-\/]+$/
1970
+ if /^[\w\-\/]+$/.match?(action)
1918
1971
  default_action ||= action.tr("-", "_") unless action.include?("/")
1919
1972
  else
1920
1973
  action = nil
@@ -1926,7 +1979,7 @@ module ActionDispatch
1926
1979
  name_for_action(options.delete(:as), action)
1927
1980
  end
1928
1981
 
1929
- path = Mapping.normalize_path URI.parser.escape(path), formatted
1982
+ path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
1930
1983
  ast = Journey::Parser.parse path
1931
1984
 
1932
1985
  mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
@@ -1934,9 +1987,7 @@ module ActionDispatch
1934
1987
  end
1935
1988
 
1936
1989
  def match_root_route(options)
1937
- name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
1938
- args = ["/", { as: name, via: :get }.merge!(options)]
1939
-
1990
+ args = ["/", { as: :root, via: :get }.merge(options)]
1940
1991
  match(*args)
1941
1992
  end
1942
1993
  end
@@ -2052,7 +2103,7 @@ module ActionDispatch
2052
2103
  # of routing helpers, e.g:
2053
2104
  #
2054
2105
  # direct :homepage do
2055
- # "http://www.rubyonrails.org"
2106
+ # "https://rubyonrails.org"
2056
2107
  # end
2057
2108
  #
2058
2109
  # direct :commentable do |model|
@@ -2251,6 +2302,7 @@ module ActionDispatch
2251
2302
 
2252
2303
  def initialize(set) #:nodoc:
2253
2304
  @set = set
2305
+ @draw_paths = set.draw_paths
2254
2306
  @scope = Scope.new(path_names: @set.resources_path_names)
2255
2307
  @concerns = {}
2256
2308
  end