actionpack 6.0.3 → 6.1.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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +246 -217
  3. data/MIT-LICENSE +1 -1
  4. data/lib/abstract_controller.rb +1 -0
  5. data/lib/abstract_controller/base.rb +35 -2
  6. data/lib/abstract_controller/callbacks.rb +2 -2
  7. data/lib/abstract_controller/helpers.rb +105 -90
  8. data/lib/abstract_controller/rendering.rb +9 -9
  9. data/lib/abstract_controller/translation.rb +8 -2
  10. data/lib/action_controller.rb +2 -3
  11. data/lib/action_controller/api.rb +2 -2
  12. data/lib/action_controller/base.rb +4 -2
  13. data/lib/action_controller/caching.rb +0 -1
  14. data/lib/action_controller/log_subscriber.rb +3 -3
  15. data/lib/action_controller/metal.rb +2 -2
  16. data/lib/action_controller/metal/conditional_get.rb +10 -2
  17. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  18. data/lib/action_controller/metal/data_streaming.rb +1 -1
  19. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
  20. data/lib/action_controller/metal/exceptions.rb +33 -0
  21. data/lib/action_controller/metal/feature_policy.rb +46 -0
  22. data/lib/action_controller/metal/head.rb +7 -4
  23. data/lib/action_controller/metal/helpers.rb +11 -1
  24. data/lib/action_controller/metal/http_authentication.rb +4 -2
  25. data/lib/action_controller/metal/implicit_render.rb +1 -1
  26. data/lib/action_controller/metal/instrumentation.rb +11 -9
  27. data/lib/action_controller/metal/live.rb +1 -1
  28. data/lib/action_controller/metal/logging.rb +20 -0
  29. data/lib/action_controller/metal/mime_responds.rb +6 -2
  30. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  31. data/lib/action_controller/metal/params_wrapper.rb +14 -8
  32. data/lib/action_controller/metal/redirecting.rb +1 -1
  33. data/lib/action_controller/metal/rendering.rb +6 -0
  34. data/lib/action_controller/metal/request_forgery_protection.rb +74 -30
  35. data/lib/action_controller/metal/rescue.rb +1 -1
  36. data/lib/action_controller/metal/strong_parameters.rb +107 -15
  37. data/lib/action_controller/renderer.rb +24 -13
  38. data/lib/action_controller/test_case.rb +62 -56
  39. data/lib/action_dispatch.rb +3 -2
  40. data/lib/action_dispatch/http/cache.rb +12 -10
  41. data/lib/action_dispatch/http/content_disposition.rb +2 -2
  42. data/lib/action_dispatch/http/content_security_policy.rb +5 -1
  43. data/lib/action_dispatch/http/feature_policy.rb +168 -0
  44. data/lib/action_dispatch/http/filter_parameters.rb +1 -1
  45. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  46. data/lib/action_dispatch/http/headers.rb +3 -2
  47. data/lib/action_dispatch/http/mime_negotiation.rb +20 -8
  48. data/lib/action_dispatch/http/mime_type.rb +28 -15
  49. data/lib/action_dispatch/http/parameters.rb +1 -19
  50. data/lib/action_dispatch/http/request.rb +26 -8
  51. data/lib/action_dispatch/http/response.rb +17 -16
  52. data/lib/action_dispatch/http/url.rb +3 -2
  53. data/lib/action_dispatch/journey.rb +0 -2
  54. data/lib/action_dispatch/journey/formatter.rb +53 -28
  55. data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
  56. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  57. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
  58. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  59. data/lib/action_dispatch/journey/nodes/node.rb +4 -3
  60. data/lib/action_dispatch/journey/parser.rb +13 -13
  61. data/lib/action_dispatch/journey/parser.y +1 -1
  62. data/lib/action_dispatch/journey/path/pattern.rb +13 -18
  63. data/lib/action_dispatch/journey/route.rb +7 -18
  64. data/lib/action_dispatch/journey/router.rb +26 -30
  65. data/lib/action_dispatch/journey/router/utils.rb +6 -4
  66. data/lib/action_dispatch/middleware/actionable_exceptions.rb +9 -2
  67. data/lib/action_dispatch/middleware/cookies.rb +74 -33
  68. data/lib/action_dispatch/middleware/debug_exceptions.rb +10 -17
  69. data/lib/action_dispatch/middleware/debug_view.rb +1 -1
  70. data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -17
  71. data/lib/action_dispatch/middleware/host_authorization.rb +23 -3
  72. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -1
  73. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  74. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  75. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  76. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  77. data/lib/action_dispatch/middleware/ssl.rb +9 -6
  78. data/lib/action_dispatch/middleware/stack.rb +18 -0
  79. data/lib/action_dispatch/middleware/static.rb +154 -93
  80. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +18 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  82. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  83. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
  84. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -8
  85. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  86. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
  87. data/lib/action_dispatch/railtie.rb +3 -2
  88. data/lib/action_dispatch/request/session.rb +2 -8
  89. data/lib/action_dispatch/request/utils.rb +26 -2
  90. data/lib/action_dispatch/routing/inspector.rb +8 -7
  91. data/lib/action_dispatch/routing/mapper.rb +102 -71
  92. data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
  93. data/lib/action_dispatch/routing/redirection.rb +3 -3
  94. data/lib/action_dispatch/routing/route_set.rb +49 -41
  95. data/lib/action_dispatch/routing/url_for.rb +1 -0
  96. data/lib/action_dispatch/system_test_case.rb +29 -24
  97. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  98. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  99. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  100. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  101. data/lib/action_dispatch/testing/assertions.rb +1 -1
  102. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  103. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  104. data/lib/action_dispatch/testing/integration.rb +38 -27
  105. data/lib/action_dispatch/testing/test_process.rb +29 -4
  106. data/lib/action_dispatch/testing/test_request.rb +3 -3
  107. data/lib/action_pack.rb +1 -1
  108. data/lib/action_pack/gem_version.rb +3 -3
  109. metadata +20 -21
  110. data/lib/action_controller/metal/force_ssl.rb +0 -58
  111. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  112. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  113. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  114. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -0,0 +1,18 @@
1
+ <% if exception.respond_to?(:original_message) && exception.respond_to?(:corrections) %>
2
+ <h2><%= h exception.original_message %></h2>
3
+ <%
4
+ # The 'did_you_mean' gem can raise exceptions when calling #corrections on
5
+ # the exception. If it does there are no corrections to show.
6
+ corrections = exception.corrections rescue []
7
+ %>
8
+ <% if corrections.any? %>
9
+ <b>Did you mean?</b>
10
+ <ul>
11
+ <% corrections.each do |correction| %>
12
+ <li style="list-style-type: none"><%= h correction %></li>
13
+ <% end %>
14
+ </ul>
15
+ <% end %>
16
+ <% else %>
17
+ <h2><%= h exception.message %></h2>
18
+ <% end %>
@@ -8,11 +8,8 @@
8
8
  </header>
9
9
 
10
10
  <div id="container">
11
- <h2>
12
- <%= h @exception.message %>
13
-
14
- <%= render "rescues/actions", exception: @exception, request: @request %>
15
- </h2>
11
+ <%= render "rescues/message_and_suggestions", exception: @exception %>
12
+ <%= render "rescues/actions", exception: @exception, request: @request %>
16
13
 
17
14
  <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx, error_index: 0 %>
18
15
  <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show, error_index: 0 %>
@@ -11,10 +11,10 @@
11
11
  <h2>
12
12
  <%= h @exception.message %>
13
13
  <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
14
- <br />To resolve this issue run: rails active_storage:install
14
+ <br />To resolve this issue run: bin/rails active_storage:install
15
15
  <% end %>
16
16
  <% if defined?(ActionMailbox) && @exception.message.match?(%r{#{ActionMailbox::InboundEmail.table_name}}) %>
17
- <br />To resolve this issue run: rails action_mailbox:install
17
+ <br />To resolve this issue run: bin/rails action_mailbox:install
18
18
  <% end %>
19
19
  </h2>
20
20
 
@@ -5,9 +5,9 @@
5
5
 
6
6
  <%= @exception.message %>
7
7
  <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
8
- To resolve this issue run: rails active_storage:install
8
+ To resolve this issue run: bin/rails active_storage:install
9
9
  <% if defined?(ActionMailbox) && @exception.message.match?(%r{#{ActionMailbox::InboundEmail.table_name}}) %>
10
- To resolve this issue run: rails action_mailbox:install
10
+ To resolve this issue run: bin/rails action_mailbox:install
11
11
  <% end %>
12
12
 
13
13
  <%= render template: "rescues/_source" %>
@@ -2,11 +2,14 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
6
  <title>Action Controller: Exception caught</title>
6
7
  <style>
7
8
  body {
8
9
  background-color: #FAFAFA;
9
10
  color: #333;
11
+ color-scheme: light dark;
12
+ supported-color-schemes: light dark;
10
13
  margin: 0px;
11
14
  }
12
15
 
@@ -30,18 +33,19 @@
30
33
 
31
34
  header {
32
35
  color: #F0F0F0;
33
- background: #C52F24;
36
+ background: #C00;
34
37
  padding: 0.5em 1.5em;
35
38
  }
36
39
 
37
40
  h1 {
41
+ overflow-wrap: break-word;
38
42
  margin: 0.2em 0;
39
43
  line-height: 1.1em;
40
44
  font-size: 2em;
41
45
  }
42
46
 
43
47
  h2 {
44
- color: #C52F24;
48
+ color: #C00;
45
49
  line-height: 25px;
46
50
  }
47
51
 
@@ -50,7 +54,7 @@
50
54
  border-radius: 4px;
51
55
  margin: 1em 0px;
52
56
  display: block;
53
- width: 978px;
57
+ max-width: 978px;
54
58
  }
55
59
 
56
60
  .summary {
@@ -78,7 +82,7 @@
78
82
  .source {
79
83
  border: 1px solid #D9D9D9;
80
84
  background: #ECECEC;
81
- width: 978px;
85
+ max-width: 978px;
82
86
  }
83
87
 
84
88
  .source pre {
@@ -114,22 +118,98 @@
114
118
  }
115
119
 
116
120
  .line.active {
117
- background-color: #FFCCCC;
121
+ background-color: #FCC;
118
122
  }
119
123
 
120
124
  .button_to {
121
125
  display: inline-block;
126
+ margin-top: 0.75em;
127
+ margin-bottom: 0.75em;
122
128
  }
123
129
 
124
130
  .hidden {
125
131
  display: none;
126
132
  }
127
133
 
134
+ input[type="submit"] {
135
+ color: white;
136
+ background-color: #C00;
137
+ border: none;
138
+ border-radius: 12px;
139
+ box-shadow: 0 3px #F99;
140
+ font-size: 13px;
141
+ font-weight: bold;
142
+ margin: 0;
143
+ padding: 10px 18px;
144
+ -webkit-appearance: none;
145
+ }
146
+ input[type="submit"]:focus,
147
+ input[type="submit"]:hover {
148
+ opacity: 0.8;
149
+ }
150
+ input[type="submit"]:active {
151
+ box-shadow: 0 2px #F99;
152
+ transform: translateY(1px)
153
+ }
154
+
155
+
128
156
  a { color: #980905; }
129
157
  a:visited { color: #666; }
130
- a.trace-frames { color: #666; }
131
- a:hover { color: #C52F24; }
132
- a.trace-frames.selected { color: #C52F24 }
158
+ a.trace-frames {
159
+ color: #666;
160
+ overflow-wrap: break-word;
161
+ }
162
+ a:hover { color: #C00; }
163
+ a.trace-frames.selected { color: #C00 }
164
+
165
+ @media (prefers-color-scheme: dark) {
166
+ body {
167
+ background-color: #222;
168
+ color: #ECECEC;
169
+ }
170
+
171
+ .details {
172
+ border-color: #666;
173
+ }
174
+
175
+ .summary {
176
+ border-color: #666;
177
+ }
178
+
179
+ .source {
180
+ border-color: #555;
181
+ background-color: #333;
182
+ }
183
+
184
+ .source .data {
185
+ background: #444;
186
+ }
187
+
188
+ .source .data .line_numbers {
189
+ background: #333;
190
+ border-color: #222;
191
+ }
192
+
193
+ .line:hover {
194
+ background: #666;
195
+ }
196
+
197
+ .line.active {
198
+ background-color: #900;
199
+ }
200
+
201
+ input[type="submit"] {
202
+ box-shadow: 0 3px #800;
203
+ }
204
+ input[type="submit"]:active {
205
+ box-shadow: 0 2px #800;
206
+ }
207
+
208
+ a { color: #C00; }
209
+ a.trace-frames { color: #999; }
210
+ a:hover { color: #E9382B; }
211
+ a.trace-frames.selected { color: #E9382B; }
212
+ }
133
213
 
134
214
  <%= yield :style %>
135
215
  </style>
@@ -2,5 +2,5 @@
2
2
  <h1>Unknown action</h1>
3
3
  </header>
4
4
  <div id="container">
5
- <h2><%= h @exception.message %></h2>
5
+ <%= render "rescues/message_and_suggestions", exception: @exception %>
6
6
  </div>
@@ -49,6 +49,17 @@
49
49
  width: 80%;
50
50
  font-size: inherit;
51
51
  }
52
+
53
+ @media (prefers-color-scheme: dark) {
54
+ #route_table tbody tr:nth-child(odd) {
55
+ background: #333;
56
+ }
57
+
58
+ #route_table tbody.exact_matches,
59
+ #route_table tbody.fuzzy_matches {
60
+ color: #333;
61
+ }
62
+ }
52
63
  <% end %>
53
64
 
54
65
  <table id='route_table' class='route_table'>
@@ -85,7 +96,7 @@
85
96
  </table>
86
97
 
87
98
  <script type='text/javascript'>
88
- // support forEarch iterator on NodeList
99
+ // support forEach iterator on NodeList
89
100
  NodeList.prototype.forEach = Array.prototype.forEach;
90
101
 
91
102
  // Enables path search functionality
@@ -23,7 +23,7 @@ module ActionDispatch
23
23
  config.action_dispatch.use_authenticated_cookie_encryption = false
24
24
  config.action_dispatch.use_cookies_with_metadata = false
25
25
  config.action_dispatch.perform_deep_munge = true
26
- config.action_dispatch.return_only_media_type_on_content_type = true
26
+ config.action_dispatch.request_id_header = "X-Request-Id"
27
27
 
28
28
  config.action_dispatch.default_headers = {
29
29
  "X-Frame-Options" => "SAMEORIGIN",
@@ -39,13 +39,14 @@ module ActionDispatch
39
39
  config.eager_load_namespaces << ActionDispatch
40
40
 
41
41
  initializer "action_dispatch.configure" do |app|
42
+ ActionDispatch::Http::URL.secure_protocol = app.config.force_ssl
42
43
  ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
43
44
  ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
44
45
  ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
46
+
45
47
  ActiveSupport.on_load(:action_dispatch_response) do
46
48
  self.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
47
49
  self.default_headers = app.config.action_dispatch.default_headers
48
- self.return_only_media_type_on_content_type = app.config.action_dispatch.return_only_media_type_on_content_type
49
50
  end
50
51
 
51
52
  ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
@@ -158,7 +158,7 @@ module ActionDispatch
158
158
  # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
159
159
  def update(hash)
160
160
  load_for_write!
161
- @delegate.update stringify_keys(hash)
161
+ @delegate.update hash.stringify_keys
162
162
  end
163
163
 
164
164
  # Deletes given key from the session.
@@ -233,15 +233,9 @@ module ActionDispatch
233
233
  def load!
234
234
  id, session = @by.load_session @req
235
235
  options[:id] = id
236
- @delegate.replace(stringify_keys(session))
236
+ @delegate.replace(session.stringify_keys)
237
237
  @loaded = true
238
238
  end
239
-
240
- def stringify_keys(other)
241
- other.each_with_object({}) { |(key, value), hash|
242
- hash[key.to_s] = value
243
- }
244
- end
245
239
  end
246
240
  end
247
241
  end
@@ -41,6 +41,10 @@ module ActionDispatch
41
41
  end
42
42
  end
43
43
 
44
+ def self.set_binary_encoding(request, params, controller, action)
45
+ CustomParamEncoder.encode(request, params, controller, action)
46
+ end
47
+
44
48
  class ParamEncoder # :nodoc:
45
49
  # Convert nested Hash to HashWithIndifferentAccess.
46
50
  def self.normalize_encode_params(params)
@@ -51,8 +55,8 @@ module ActionDispatch
51
55
  if params.has_key?(:tempfile)
52
56
  ActionDispatch::Http::UploadedFile.new(params)
53
57
  else
54
- params.each_with_object({}) do |(key, val), new_hash|
55
- new_hash[key] = normalize_encode_params(val)
58
+ params.transform_values do |val|
59
+ normalize_encode_params(val)
56
60
  end.with_indifferent_access
57
61
  end
58
62
  else
@@ -73,6 +77,26 @@ module ActionDispatch
73
77
  list
74
78
  end
75
79
  end
80
+
81
+ class CustomParamEncoder # :nodoc:
82
+ def self.encode(request, params, controller, action)
83
+ return params unless controller && controller.valid_encoding? && encoding_template = action_encoding_template(request, controller, action)
84
+ params.except(:controller, :action).each do |key, value|
85
+ ActionDispatch::Request::Utils.each_param_value(value) do |param|
86
+ if encoding_template[key.to_s]
87
+ param.force_encoding(encoding_template[key.to_s])
88
+ end
89
+ end
90
+ end
91
+ params
92
+ end
93
+
94
+ def self.action_encoding_template(request, controller, action) # :nodoc:
95
+ request.controller_class_for(controller).action_encoding_template(action)
96
+ rescue MissingController
97
+ nil
98
+ end
99
+ end
76
100
  end
77
101
  end
78
102
  end
@@ -53,7 +53,7 @@ module ActionDispatch
53
53
 
54
54
  ##
55
55
  # This class is just used for displaying route information when someone
56
- # executes `rails routes` or looks at the RoutingError page.
56
+ # executes `bin/rails routes` or looks at the RoutingError page.
57
57
  # People should not use this class.
58
58
  class RoutesInspector # :nodoc:
59
59
  def initialize(routes)
@@ -94,7 +94,7 @@ module ActionDispatch
94
94
  if filter
95
95
  @routes.select do |route|
96
96
  route_wrapper = RouteWrapper.new(route)
97
- filter.any? { |default, value| route_wrapper.send(default) =~ value }
97
+ filter.any? { |default, value| value.match?(route_wrapper.send(default)) }
98
98
  end
99
99
  else
100
100
  @routes
@@ -200,6 +200,11 @@ module ActionDispatch
200
200
  end
201
201
 
202
202
  class Expanded < Base
203
+ def initialize(width: IO.console_size[1])
204
+ @width = width
205
+ super()
206
+ end
207
+
203
208
  def section_title(title)
204
209
  @buffer << "\n#{"[ #{title} ]"}"
205
210
  end
@@ -222,11 +227,7 @@ module ActionDispatch
222
227
  end
223
228
 
224
229
  def route_header(index:)
225
- console_width = IO.console_size.second
226
- header_prefix = "--[ Route #{index} ]"
227
- dash_remainder = [console_width - header_prefix.size, 0].max
228
-
229
- "#{header_prefix}#{'-' * dash_remainder}"
230
+ "--[ Route #{index} ]".ljust(@width, "-")
230
231
  end
231
232
  end
232
233
  end
@@ -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
 
@@ -74,13 +75,17 @@ module ActionDispatch
74
75
  :default_action, :required_defaults, :ast, :scope_options
75
76
 
76
77
  def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
77
- options = scope[:options].merge(options) if scope[:options]
78
-
79
- defaults = (scope[:defaults] || {}).dup
80
- scope_constraints = scope[:constraints] || {}
81
- scope_options = scope[:options] || {}
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
+ }
82
85
 
83
- new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope_options, 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)
84
89
  end
85
90
 
86
91
  def self.check_via(via)
@@ -108,13 +113,12 @@ module ActionDispatch
108
113
  end
109
114
 
110
115
  def self.optional_format?(path, format)
111
- format != false && path !~ OPTIONAL_FORMAT_REGEX
116
+ format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
112
117
  end
113
118
 
114
- def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, scope_options, blocks, via, options_constraints, anchor, options)
115
- @defaults = defaults
116
- @set = set
117
-
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
118
122
  @to = intern(to)
119
123
  @default_controller = intern(controller)
120
124
  @default_action = intern(default_action)
@@ -122,23 +126,35 @@ module ActionDispatch
122
126
  @anchor = anchor
123
127
  @via = via
124
128
  @internal = options.delete(:internal)
125
- @scope_options = scope_options
126
-
127
- path_params = ast.find_all(&:symbol?).map(&:to_sym)
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
128
144
 
129
- options = add_wildcard_options(options, formatted, ast)
145
+ options = wildcard_options.merge!(options)
130
146
 
131
- options = normalize_options!(options, path_params, modyoule)
147
+ options = normalize_options!(options, path_params, scope_params[:module])
132
148
 
133
149
  split_options = constraints(options, path_params)
134
150
 
135
- constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
151
+ constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
136
152
 
137
153
  if options_constraints.is_a?(Hash)
138
154
  @defaults = Hash[options_constraints.find_all { |key, default|
139
155
  URL_OPTIONS.include?(key) && (String === default || Integer === default)
140
156
  }].merge @defaults
141
- @blocks = blocks
157
+ @blocks = scope_params[:blocks]
142
158
  constraints.merge! options_constraints
143
159
  else
144
160
  @blocks = blocks(options_constraints)
@@ -161,16 +177,20 @@ module ActionDispatch
161
177
  end
162
178
 
163
179
  def make_route(name, precedence)
164
- Journey::Route.new(name, application, path, conditions, required_defaults,
165
- defaults, request_method, precedence, scope_options, @internal)
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)
166
184
  end
167
185
 
168
186
  def application
169
187
  app(@blocks)
170
188
  end
171
189
 
190
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
191
+
172
192
  def path
173
- build_path @ast, requirements, @anchor
193
+ Journey::Path::Pattern.new(@ast, requirements, JOINED_SEPARATORS, @anchor)
174
194
  end
175
195
 
176
196
  def conditions
@@ -191,16 +211,10 @@ module ActionDispatch
191
211
  end
192
212
  private :request_method
193
213
 
194
- JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
195
-
196
- def build_path(ast, requirements, anchor)
197
- pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
198
-
214
+ private
199
215
  # Find all the symbol nodes that are adjacent to literal nodes and alter
200
216
  # the regexp so that Journey will partition them into custom routes.
201
- ast.find_all { |node|
202
- next unless node.cat?
203
-
217
+ def alter_regex_for_custom_routes(node)
204
218
  if node.left.literal? && node.right.symbol?
205
219
  symbol = node.right
206
220
  elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
@@ -209,36 +223,17 @@ module ActionDispatch
209
223
  symbol = node.left
210
224
  elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
211
225
  symbol = node.left
212
- else
213
- next
214
226
  end
215
227
 
216
228
  if symbol
217
229
  symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
218
230
  end
219
- }
220
-
221
- pattern
222
- end
223
- private :build_path
231
+ end
224
232
 
225
- private
226
233
  def intern(object)
227
234
  object.is_a?(String) ? -object : object
228
235
  end
229
236
 
230
- def add_wildcard_options(options, formatted, path_ast)
231
- # Add a constraint for wildcard route to make it non-greedy and match the
232
- # optional format part of the route by default.
233
- if formatted != false
234
- path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
235
- hash[node.name.to_sym] ||= /.+?/
236
- }.merge options
237
- else
238
- options
239
- end
240
- end
241
-
242
237
  def normalize_options!(options, path_params, modyoule)
243
238
  if path_params.include?(:controller)
244
239
  raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
@@ -342,7 +337,7 @@ module ActionDispatch
342
337
 
343
338
  def split_to(to)
344
339
  if /#/.match?(to)
345
- to.split("#")
340
+ to.split("#").map!(&:-@)
346
341
  else
347
342
  []
348
343
  end
@@ -350,10 +345,10 @@ module ActionDispatch
350
345
 
351
346
  def add_controller_module(controller, modyoule)
352
347
  if modyoule && !controller.is_a?(Regexp)
353
- if %r{\A/}.match?(controller)
354
- controller[1..-1]
348
+ if controller&.start_with?("/")
349
+ -controller[1..-1]
355
350
  else
356
- [modyoule, controller].compact.join("/")
351
+ -[modyoule, controller].compact.join("/")
357
352
  end
358
353
  else
359
354
  controller
@@ -393,12 +388,23 @@ module ActionDispatch
393
388
  end
394
389
  end
395
390
 
396
- # Invokes Journey::Router::Utils.normalize_path and ensure that
397
- # (:locale) becomes (/:locale) instead of /(:locale). Except
398
- # 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.
399
394
  def self.normalize_path(path)
400
395
  path = Journey::Router::Utils.normalize_path(path)
401
- path.gsub!(%r{/(\(+)/?}, '\1/') unless %r{^/(\(+[^)]+\)){1,}$}.match?(path)
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)
402
408
  path
403
409
  end
404
410
 
@@ -555,7 +561,7 @@ module ActionDispatch
555
561
  # Constrains parameters with a hash of regular expressions
556
562
  # or an object that responds to <tt>matches?</tt>. In addition, constraints
557
563
  # other than path can also be specified with any object
558
- # that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
564
+ # that responds to <tt>===</tt> (e.g. String, Array, Range, etc.).
559
565
  #
560
566
  # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
561
567
  #
@@ -684,7 +690,7 @@ module ActionDispatch
684
690
 
685
691
  # We must actually delete prefix segment keys to avoid passing them to next url_for.
686
692
  _route.segment_keys.each { |k| options.delete(k) }
687
- _url_helpers.send("#{name}_path", prefix_options)
693
+ _url_helpers.public_send("#{name}_path", prefix_options)
688
694
  end
689
695
 
690
696
  app.routes.define_mounted_helper(name, script_namer)
@@ -744,6 +750,14 @@ module ActionDispatch
744
750
  map_method(:delete, args, &block)
745
751
  end
746
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
+
747
761
  private
748
762
  def map_method(method, args, &block)
749
763
  options = args.extract_options!
@@ -991,7 +1005,7 @@ module ActionDispatch
991
1005
  #
992
1006
  # Requests to routes can be constrained based on specific criteria:
993
1007
  #
994
- # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
1008
+ # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
995
1009
  # resources :iphones
996
1010
  # end
997
1011
  #
@@ -1001,7 +1015,7 @@ module ActionDispatch
1001
1015
  #
1002
1016
  # class Iphone
1003
1017
  # def self.matches?(request)
1004
- # request.env["HTTP_USER_AGENT"] =~ /iPhone/
1018
+ # /iPhone/.match?(request.env["HTTP_USER_AGENT"])
1005
1019
  # end
1006
1020
  # end
1007
1021
  #
@@ -1594,6 +1608,22 @@ module ActionDispatch
1594
1608
  !parent_resource.singleton? && @scope[:shallow]
1595
1609
  end
1596
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
+
1597
1627
  # Matches a URL pattern to one or more routes.
1598
1628
  # For more information, see match[rdoc-ref:Base#match].
1599
1629
  #
@@ -1674,20 +1704,20 @@ module ActionDispatch
1674
1704
 
1675
1705
  def apply_common_behavior_for(method, resources, options, &block)
1676
1706
  if resources.length > 1
1677
- resources.each { |r| send(method, r, options, &block) }
1707
+ resources.each { |r| public_send(method, r, options, &block) }
1678
1708
  return true
1679
1709
  end
1680
1710
 
1681
1711
  if options[:shallow]
1682
1712
  options.delete(:shallow)
1683
1713
  shallow do
1684
- send(method, resources.pop, options, &block)
1714
+ public_send(method, resources.pop, options, &block)
1685
1715
  end
1686
1716
  return true
1687
1717
  end
1688
1718
 
1689
1719
  if resource_scope?
1690
- nested { send(method, resources.pop, options, &block) }
1720
+ nested { public_send(method, resources.pop, options, &block) }
1691
1721
  return true
1692
1722
  end
1693
1723
 
@@ -1698,7 +1728,7 @@ module ActionDispatch
1698
1728
  scope_options = options.slice!(*RESOURCE_OPTIONS)
1699
1729
  unless scope_options.empty?
1700
1730
  scope(scope_options) do
1701
- send(method, resources.pop, options, &block)
1731
+ public_send(method, resources.pop, options, &block)
1702
1732
  end
1703
1733
  return true
1704
1734
  end
@@ -1828,7 +1858,7 @@ module ActionDispatch
1828
1858
  # and return nil in case it isn't. Otherwise, we pass the invalid name
1829
1859
  # forward so the underlying router engine treats it and raises an exception.
1830
1860
  if as.nil?
1831
- candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
1861
+ candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
1832
1862
  else
1833
1863
  candidate
1834
1864
  end
@@ -1882,7 +1912,7 @@ module ActionDispatch
1882
1912
  options_constraints = options.delete(:constraints) || {}
1883
1913
 
1884
1914
  path_types = paths.group_by(&:class)
1885
- path_types.fetch(String, []).each do |_path|
1915
+ (path_types[String] || []).each do |_path|
1886
1916
  route_options = options.dup
1887
1917
  if _path && option_path
1888
1918
  raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
@@ -1891,7 +1921,7 @@ module ActionDispatch
1891
1921
  decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1892
1922
  end
1893
1923
 
1894
- path_types.fetch(Symbol, []).each do |action|
1924
+ (path_types[Symbol] || []).each do |action|
1895
1925
  route_options = options.dup
1896
1926
  decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
1897
1927
  end
@@ -1904,14 +1934,14 @@ module ActionDispatch
1904
1934
 
1905
1935
  path_without_format = path.sub(/\(\.:format\)$/, "")
1906
1936
  if using_match_shorthand?(path_without_format)
1907
- path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1937
+ path_without_format.delete_prefix("/").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1908
1938
  else
1909
1939
  nil
1910
1940
  end
1911
1941
  end
1912
1942
 
1913
1943
  def using_match_shorthand?(path)
1914
- path =~ %r{^/?[-\w]+/[-\w/]+$}
1944
+ %r{^/?[-\w]+/[-\w/]+$}.match?(path)
1915
1945
  end
1916
1946
 
1917
1947
  def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
@@ -1949,7 +1979,7 @@ module ActionDispatch
1949
1979
  name_for_action(options.delete(:as), action)
1950
1980
  end
1951
1981
 
1952
- path = Mapping.normalize_path URI.parser.escape(path), formatted
1982
+ path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
1953
1983
  ast = Journey::Parser.parse path
1954
1984
 
1955
1985
  mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
@@ -2272,6 +2302,7 @@ module ActionDispatch
2272
2302
 
2273
2303
  def initialize(set) #:nodoc:
2274
2304
  @set = set
2305
+ @draw_paths = set.draw_paths
2275
2306
  @scope = Scope.new(path_names: @set.resources_path_names)
2276
2307
  @concerns = {}
2277
2308
  end