actionpack 7.1.5.1 → 8.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +308 -523
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +6 -2
  5. data/lib/abstract_controller/base.rb +104 -105
  6. data/lib/abstract_controller/caching/fragments.rb +50 -53
  7. data/lib/abstract_controller/caching.rb +8 -3
  8. data/lib/abstract_controller/callbacks.rb +70 -62
  9. data/lib/abstract_controller/collector.rb +7 -7
  10. data/lib/abstract_controller/deprecator.rb +2 -0
  11. data/lib/abstract_controller/error.rb +2 -0
  12. data/lib/abstract_controller/helpers.rb +71 -84
  13. data/lib/abstract_controller/logger.rb +4 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/rendering.rb +13 -13
  16. data/lib/abstract_controller/translation.rb +12 -13
  17. data/lib/abstract_controller/url_for.rb +8 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api/api_rendering.rb +2 -0
  20. data/lib/action_controller/api.rb +76 -72
  21. data/lib/action_controller/base.rb +199 -126
  22. data/lib/action_controller/caching.rb +16 -14
  23. data/lib/action_controller/deprecator.rb +2 -0
  24. data/lib/action_controller/form_builder.rb +21 -18
  25. data/lib/action_controller/log_subscriber.rb +23 -2
  26. data/lib/action_controller/metal/allow_browser.rb +133 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  28. data/lib/action_controller/metal/conditional_get.rb +217 -175
  29. data/lib/action_controller/metal/content_security_policy.rb +25 -24
  30. data/lib/action_controller/metal/cookies.rb +4 -2
  31. data/lib/action_controller/metal/data_streaming.rb +72 -63
  32. data/lib/action_controller/metal/default_headers.rb +5 -3
  33. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  34. data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
  35. data/lib/action_controller/metal/exceptions.rb +16 -9
  36. data/lib/action_controller/metal/flash.rb +13 -14
  37. data/lib/action_controller/metal/head.rb +15 -11
  38. data/lib/action_controller/metal/helpers.rb +63 -55
  39. data/lib/action_controller/metal/http_authentication.rb +209 -201
  40. data/lib/action_controller/metal/implicit_render.rb +17 -15
  41. data/lib/action_controller/metal/instrumentation.rb +16 -14
  42. data/lib/action_controller/metal/live.rb +177 -128
  43. data/lib/action_controller/metal/logging.rb +6 -4
  44. data/lib/action_controller/metal/mime_responds.rb +151 -142
  45. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  46. data/lib/action_controller/metal/params_wrapper.rb +57 -59
  47. data/lib/action_controller/metal/permissions_policy.rb +22 -12
  48. data/lib/action_controller/metal/rate_limiting.rb +92 -0
  49. data/lib/action_controller/metal/redirecting.rb +213 -94
  50. data/lib/action_controller/metal/renderers.rb +78 -57
  51. data/lib/action_controller/metal/rendering.rb +111 -77
  52. data/lib/action_controller/metal/request_forgery_protection.rb +182 -143
  53. data/lib/action_controller/metal/rescue.rb +20 -9
  54. data/lib/action_controller/metal/streaming.rb +118 -195
  55. data/lib/action_controller/metal/strong_parameters.rb +720 -530
  56. data/lib/action_controller/metal/testing.rb +2 -0
  57. data/lib/action_controller/metal/url_for.rb +17 -15
  58. data/lib/action_controller/metal.rb +86 -60
  59. data/lib/action_controller/railtie.rb +36 -15
  60. data/lib/action_controller/railties/helpers.rb +2 -0
  61. data/lib/action_controller/renderer.rb +41 -36
  62. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +160 -131
  65. data/lib/action_controller.rb +5 -1
  66. data/lib/action_dispatch/constants.rb +8 -0
  67. data/lib/action_dispatch/deprecator.rb +2 -0
  68. data/lib/action_dispatch/http/cache.rb +163 -35
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +54 -39
  71. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +22 -22
  74. data/lib/action_dispatch/http/mime_negotiation.rb +89 -41
  75. data/lib/action_dispatch/http/mime_type.rb +25 -21
  76. data/lib/action_dispatch/http/mime_types.rb +3 -0
  77. data/lib/action_dispatch/http/param_builder.rb +187 -0
  78. data/lib/action_dispatch/http/param_error.rb +26 -0
  79. data/lib/action_dispatch/http/parameters.rb +14 -12
  80. data/lib/action_dispatch/http/permissions_policy.rb +25 -36
  81. data/lib/action_dispatch/http/query_parser.rb +55 -0
  82. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  83. data/lib/action_dispatch/http/request.rb +141 -92
  84. data/lib/action_dispatch/http/response.rb +137 -77
  85. data/lib/action_dispatch/http/upload.rb +18 -16
  86. data/lib/action_dispatch/http/url.rb +187 -89
  87. data/lib/action_dispatch/journey/formatter.rb +21 -9
  88. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  89. data/lib/action_dispatch/journey/gtg/simulator.rb +34 -11
  90. data/lib/action_dispatch/journey/gtg/transition_table.rb +47 -53
  91. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  92. data/lib/action_dispatch/journey/nodes/node.rb +8 -6
  93. data/lib/action_dispatch/journey/parser.rb +99 -195
  94. data/lib/action_dispatch/journey/path/pattern.rb +4 -1
  95. data/lib/action_dispatch/journey/route.rb +54 -38
  96. data/lib/action_dispatch/journey/router/utils.rb +22 -27
  97. data/lib/action_dispatch/journey/router.rb +63 -83
  98. data/lib/action_dispatch/journey/routes.rb +11 -2
  99. data/lib/action_dispatch/journey/scanner.rb +46 -42
  100. data/lib/action_dispatch/journey/visitors.rb +57 -23
  101. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  102. data/lib/action_dispatch/journey.rb +2 -0
  103. data/lib/action_dispatch/log_subscriber.rb +7 -1
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
  106. data/lib/action_dispatch/middleware/callbacks.rb +3 -1
  107. data/lib/action_dispatch/middleware/cookies.rb +125 -106
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +37 -8
  109. data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
  110. data/lib/action_dispatch/middleware/debug_view.rb +13 -5
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +18 -23
  112. data/lib/action_dispatch/middleware/executor.rb +19 -4
  113. data/lib/action_dispatch/middleware/flash.rb +63 -51
  114. data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +14 -12
  116. data/lib/action_dispatch/middleware/reloader.rb +5 -3
  117. data/lib/action_dispatch/middleware/remote_ip.rb +87 -77
  118. data/lib/action_dispatch/middleware/request_id.rb +16 -10
  119. data/lib/action_dispatch/middleware/server_timing.rb +4 -2
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +30 -8
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
  125. data/lib/action_dispatch/middleware/ssl.rb +53 -40
  126. data/lib/action_dispatch/middleware/stack.rb +11 -10
  127. data/lib/action_dispatch/middleware/static.rb +33 -31
  128. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  130. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  141. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
  142. data/lib/action_dispatch/railtie.rb +23 -3
  143. data/lib/action_dispatch/request/session.rb +24 -21
  144. data/lib/action_dispatch/request/utils.rb +11 -3
  145. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  146. data/lib/action_dispatch/routing/inspector.rb +85 -60
  147. data/lib/action_dispatch/routing/mapper.rb +1031 -851
  148. data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
  149. data/lib/action_dispatch/routing/redirection.rb +47 -39
  150. data/lib/action_dispatch/routing/route_set.rb +79 -56
  151. data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
  152. data/lib/action_dispatch/routing/url_for.rb +130 -125
  153. data/lib/action_dispatch/routing.rb +150 -148
  154. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  155. data/lib/action_dispatch/system_test_case.rb +91 -81
  156. data/lib/action_dispatch/system_testing/browser.rb +16 -23
  157. data/lib/action_dispatch/system_testing/driver.rb +2 -0
  158. data/lib/action_dispatch/system_testing/server.rb +2 -0
  159. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +34 -23
  160. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  161. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  162. data/lib/action_dispatch/testing/assertions/response.rb +52 -25
  163. data/lib/action_dispatch/testing/assertions/routing.rb +168 -87
  164. data/lib/action_dispatch/testing/assertions.rb +2 -0
  165. data/lib/action_dispatch/testing/integration.rb +233 -223
  166. data/lib/action_dispatch/testing/request_encoder.rb +11 -9
  167. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  168. data/lib/action_dispatch/testing/test_process.rb +11 -8
  169. data/lib/action_dispatch/testing/test_request.rb +3 -1
  170. data/lib/action_dispatch/testing/test_response.rb +27 -26
  171. data/lib/action_dispatch.rb +36 -32
  172. data/lib/action_pack/gem_version.rb +6 -4
  173. data/lib/action_pack/version.rb +3 -1
  174. data/lib/action_pack.rb +17 -16
  175. metadata +36 -32
  176. data/lib/action_dispatch/journey/parser.y +0 -50
  177. data/lib/action_dispatch/journey/parser_extras.rb +0 -31
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/module/attribute_accessors"
4
6
 
5
7
  module ActionDispatch
@@ -9,26 +11,123 @@ module ActionDispatch
9
11
  HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
10
12
  PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
11
13
 
14
+ # DomainExtractor provides utility methods for extracting domain and subdomain
15
+ # information from host strings. This module is used internally by Action Dispatch
16
+ # to parse host names and separate the domain from subdomains based on the
17
+ # top-level domain (TLD) length.
18
+ #
19
+ # The module assumes a standard domain structure where domains consist of:
20
+ # - Subdomains (optional, can be multiple levels)
21
+ # - Domain name
22
+ # - Top-level domain (TLD, can be multiple levels like .co.uk)
23
+ #
24
+ # For example, in "api.staging.example.co.uk":
25
+ # - Subdomains: ["api", "staging"]
26
+ # - Domain: "example.co.uk" (with tld_length=2)
27
+ # - TLD: "co.uk"
28
+ module DomainExtractor
29
+ extend self
30
+
31
+ # Extracts the domain part from a host string, including the specified
32
+ # number of top-level domain components.
33
+ #
34
+ # The domain includes the main domain name plus the TLD components.
35
+ # The +tld_length+ parameter specifies how many components from the right
36
+ # should be considered part of the TLD.
37
+ #
38
+ # ==== Parameters
39
+ #
40
+ # [+host+]
41
+ # The host string to extract the domain from.
42
+ #
43
+ # [+tld_length+]
44
+ # The number of domain components that make up the TLD. For example,
45
+ # use 1 for ".com" or 2 for ".co.uk".
46
+ #
47
+ # ==== Examples
48
+ #
49
+ # # Standard TLD (tld_length = 1)
50
+ # DomainExtractor.domain_from("www.example.com", 1)
51
+ # # => "example.com"
52
+ #
53
+ # # Country-code TLD (tld_length = 2)
54
+ # DomainExtractor.domain_from("www.example.co.uk", 2)
55
+ # # => "example.co.uk"
56
+ #
57
+ # # Multiple subdomains
58
+ # DomainExtractor.domain_from("api.staging.myapp.herokuapp.com", 1)
59
+ # # => "herokuapp.com"
60
+ #
61
+ # # Single component (returns the host itself)
62
+ # DomainExtractor.domain_from("localhost", 1)
63
+ # # => "localhost"
64
+ def domain_from(host, tld_length)
65
+ host.split(".").last(1 + tld_length).join(".")
66
+ end
67
+
68
+ # Extracts the subdomain components from a host string as an Array.
69
+ #
70
+ # Returns all the components that come before the domain and TLD parts.
71
+ # The +tld_length+ parameter is used to determine where the domain begins
72
+ # so that everything before it is considered a subdomain.
73
+ #
74
+ # ==== Parameters
75
+ #
76
+ # [+host+]
77
+ # The host string to extract subdomains from.
78
+ #
79
+ # [+tld_length+]
80
+ # The number of domain components that make up the TLD. This affects
81
+ # where the domain boundary is calculated.
82
+ #
83
+ # ==== Examples
84
+ #
85
+ # # Standard TLD (tld_length = 1)
86
+ # DomainExtractor.subdomains_from("www.example.com", 1)
87
+ # # => ["www"]
88
+ #
89
+ # # Country-code TLD (tld_length = 2)
90
+ # DomainExtractor.subdomains_from("api.staging.example.co.uk", 2)
91
+ # # => ["api", "staging"]
92
+ #
93
+ # # No subdomains
94
+ # DomainExtractor.subdomains_from("example.com", 1)
95
+ # # => []
96
+ #
97
+ # # Single subdomain with complex TLD
98
+ # DomainExtractor.subdomains_from("www.mysite.co.uk", 2)
99
+ # # => ["www"]
100
+ #
101
+ # # Multiple levels of subdomains
102
+ # DomainExtractor.subdomains_from("dev.api.staging.example.com", 1)
103
+ # # => ["dev", "api", "staging"]
104
+ def subdomains_from(host, tld_length)
105
+ parts = host.split(".")
106
+ parts[0..-(tld_length + 2)]
107
+ end
108
+ end
109
+
12
110
  mattr_accessor :secure_protocol, default: false
13
111
  mattr_accessor :tld_length, default: 1
112
+ mattr_accessor :domain_extractor, default: DomainExtractor
14
113
 
15
114
  class << self
16
115
  # Returns the domain part of a host given the domain level.
17
116
  #
18
- # # Top-level domain example
19
- # extract_domain('www.example.com', 1) # => "example.com"
20
- # # Second-level domain example
21
- # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
117
+ # # Top-level domain example
118
+ # extract_domain('www.example.com', 1) # => "example.com"
119
+ # # Second-level domain example
120
+ # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
22
121
  def extract_domain(host, tld_length)
23
122
  extract_domain_from(host, tld_length) if named_host?(host)
24
123
  end
25
124
 
26
125
  # Returns the subdomains of a host as an Array given the domain level.
27
126
  #
28
- # # Top-level domain example
29
- # extract_subdomains('www.example.com', 1) # => ["www"]
30
- # # Second-level domain example
31
- # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
127
+ # # Top-level domain example
128
+ # extract_subdomains('www.example.com', 1) # => ["www"]
129
+ # # Second-level domain example
130
+ # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
32
131
  def extract_subdomains(host, tld_length)
33
132
  if named_host?(host)
34
133
  extract_subdomains_from(host, tld_length)
@@ -39,10 +138,10 @@ module ActionDispatch
39
138
 
40
139
  # Returns the subdomains of a host as a String given the domain level.
41
140
  #
42
- # # Top-level domain example
43
- # extract_subdomain('www.example.com', 1) # => "www"
44
- # # Second-level domain example
45
- # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
141
+ # # Top-level domain example
142
+ # extract_subdomain('www.example.com', 1) # => "www"
143
+ # # Second-level domain example
144
+ # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
46
145
  def extract_subdomain(host, tld_length)
47
146
  extract_subdomains(host, tld_length).join(".")
48
147
  end
@@ -94,34 +193,33 @@ module ActionDispatch
94
193
  end
95
194
 
96
195
  def extract_domain_from(host, tld_length)
97
- host.split(".").last(1 + tld_length).join(".")
196
+ domain_extractor.domain_from(host, tld_length)
98
197
  end
99
198
 
100
199
  def extract_subdomains_from(host, tld_length)
101
- parts = host.split(".")
102
- parts[0..-(tld_length + 2)]
200
+ domain_extractor.subdomains_from(host, tld_length)
103
201
  end
104
202
 
105
203
  def build_host_url(host, port, protocol, options, path)
106
204
  if match = host.match(HOST_REGEXP)
107
- protocol ||= match[1] unless protocol == false
108
- host = match[2]
109
- port = match[3] unless options.key? :port
205
+ protocol_from_host = match[1] if protocol.nil?
206
+ host = match[2]
207
+ port = match[3] unless options.key? :port
110
208
  end
111
209
 
112
- protocol = normalize_protocol protocol
210
+ protocol = protocol_from_host || normalize_protocol(protocol).dup
113
211
  host = normalize_host(host, options)
212
+ port = normalize_port(port, protocol)
114
213
 
115
- result = protocol.dup
214
+ result = protocol
116
215
 
117
216
  if options[:user] && options[:password]
118
217
  result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
119
218
  end
120
219
 
121
220
  result << host
122
- normalize_port(port, protocol) { |normalized_port|
123
- result << ":#{normalized_port}"
124
- }
221
+
222
+ result << ":" << port.to_s if port
125
223
 
126
224
  result.concat path
127
225
  end
@@ -167,11 +265,11 @@ module ActionDispatch
167
265
  return unless port
168
266
 
169
267
  case protocol
170
- when "//" then yield port
268
+ when "//" then port
171
269
  when "https://"
172
- yield port unless port.to_i == 443
270
+ port unless port.to_i == 443
173
271
  else
174
- yield port unless port.to_i == 80
272
+ port unless port.to_i == 80
175
273
  end
176
274
  end
177
275
  end
@@ -184,33 +282,33 @@ module ActionDispatch
184
282
 
185
283
  # Returns the complete URL used for this request.
186
284
  #
187
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
188
- # req.url # => "http://example.com"
285
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
286
+ # req.url # => "http://example.com"
189
287
  def url
190
288
  protocol + host_with_port + fullpath
191
289
  end
192
290
 
193
291
  # Returns 'https://' if this is an SSL request and 'http://' otherwise.
194
292
  #
195
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
196
- # req.protocol # => "http://"
293
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
294
+ # req.protocol # => "http://"
197
295
  #
198
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
199
- # req.protocol # => "https://"
296
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
297
+ # req.protocol # => "https://"
200
298
  def protocol
201
299
  @protocol ||= ssl? ? "https://" : "http://"
202
300
  end
203
301
 
204
- # Returns the \host and port for this request, such as "example.com:8080".
302
+ # Returns the host and port for this request, such as "example.com:8080".
205
303
  #
206
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
207
- # req.raw_host_with_port # => "example.com"
304
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
305
+ # req.raw_host_with_port # => "example.com"
208
306
  #
209
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
210
- # req.raw_host_with_port # => "example.com:80"
307
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
308
+ # req.raw_host_with_port # => "example.com:80"
211
309
  #
212
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
213
- # req.raw_host_with_port # => "example.com:8080"
310
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
311
+ # req.raw_host_with_port # => "example.com:8080"
214
312
  def raw_host_with_port
215
313
  if forwarded = x_forwarded_host.presence
216
314
  forwarded.split(/,\s?/).last
@@ -221,35 +319,35 @@ module ActionDispatch
221
319
 
222
320
  # Returns the host for this request, such as "example.com".
223
321
  #
224
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
225
- # req.host # => "example.com"
322
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
323
+ # req.host # => "example.com"
226
324
  def host
227
325
  raw_host_with_port.sub(/:\d+$/, "")
228
326
  end
229
327
 
230
- # Returns a \host:\port string for this request, such as "example.com" or
231
- # "example.com:8080". Port is only included if it is not a default port
232
- # (80 or 443)
328
+ # Returns a host:port string for this request, such as "example.com" or
329
+ # "example.com:8080". Port is only included if it is not a default port (80 or
330
+ # 443)
233
331
  #
234
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
235
- # req.host_with_port # => "example.com"
332
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
333
+ # req.host_with_port # => "example.com"
236
334
  #
237
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
238
- # req.host_with_port # => "example.com"
335
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
336
+ # req.host_with_port # => "example.com"
239
337
  #
240
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
241
- # req.host_with_port # => "example.com:8080"
338
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
339
+ # req.host_with_port # => "example.com:8080"
242
340
  def host_with_port
243
341
  "#{host}#{port_string}"
244
342
  end
245
343
 
246
344
  # Returns the port number of this request as an integer.
247
345
  #
248
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
249
- # req.port # => 80
346
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
347
+ # req.port # => 80
250
348
  #
251
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
252
- # req.port # => 8080
349
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
350
+ # req.port # => 8080
253
351
  def port
254
352
  @port ||= if raw_host_with_port =~ /:(\d+)$/
255
353
  $1.to_i
@@ -258,10 +356,10 @@ module ActionDispatch
258
356
  end
259
357
  end
260
358
 
261
- # Returns the standard \port number for this request's protocol.
359
+ # Returns the standard port number for this request's protocol.
262
360
  #
263
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
264
- # req.standard_port # => 80
361
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
362
+ # req.standard_port # => 80
265
363
  def standard_port
266
364
  if "https://" == protocol
267
365
  443
@@ -270,70 +368,70 @@ module ActionDispatch
270
368
  end
271
369
  end
272
370
 
273
- # Returns whether this request is using the standard port
371
+ # Returns whether this request is using the standard port.
274
372
  #
275
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
276
- # req.standard_port? # => true
373
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
374
+ # req.standard_port? # => true
277
375
  #
278
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
279
- # req.standard_port? # => false
376
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
377
+ # req.standard_port? # => false
280
378
  def standard_port?
281
379
  port == standard_port
282
380
  end
283
381
 
284
- # Returns a number \port suffix like 8080 if the \port number of this request
285
- # is not the default HTTP \port 80 or HTTPS \port 443.
382
+ # Returns a number port suffix like 8080 if the port number of this request is
383
+ # not the default HTTP port 80 or HTTPS port 443.
286
384
  #
287
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
288
- # req.optional_port # => nil
385
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
386
+ # req.optional_port # => nil
289
387
  #
290
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
291
- # req.optional_port # => 8080
388
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
389
+ # req.optional_port # => 8080
292
390
  def optional_port
293
391
  standard_port? ? nil : port
294
392
  end
295
393
 
296
- # Returns a string \port suffix, including colon, like ":8080" if the \port
297
- # number of this request is not the default HTTP \port 80 or HTTPS \port 443.
394
+ # Returns a string port suffix, including colon, like ":8080" if the port number
395
+ # of this request is not the default HTTP port 80 or HTTPS port 443.
298
396
  #
299
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
300
- # req.port_string # => ""
397
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
398
+ # req.port_string # => ""
301
399
  #
302
- # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
303
- # req.port_string # => ":8080"
400
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
401
+ # req.port_string # => ":8080"
304
402
  def port_string
305
403
  standard_port? ? "" : ":#{port}"
306
404
  end
307
405
 
308
- # Returns the requested port, such as 8080, based on SERVER_PORT
406
+ # Returns the requested port, such as 8080, based on SERVER_PORT.
309
407
  #
310
- # req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
311
- # req.server_port # => 80
408
+ # req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
409
+ # req.server_port # => 80
312
410
  #
313
- # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080'
314
- # req.server_port # => 8080
411
+ # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080'
412
+ # req.server_port # => 8080
315
413
  def server_port
316
414
  get_header("SERVER_PORT").to_i
317
415
  end
318
416
 
319
- # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
320
- # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
417
+ # Returns the domain part of a host, such as "rubyonrails.org" in
418
+ # "www.rubyonrails.org". You can specify a different `tld_length`, such as 2 to
419
+ # catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
321
420
  def domain(tld_length = @@tld_length)
322
421
  ActionDispatch::Http::URL.extract_domain(host, tld_length)
323
422
  end
324
423
 
325
- # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
326
- # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
327
- # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
328
- # in "www.rubyonrails.co.uk".
424
+ # Returns all the subdomains as an array, so `["dev", "www"]` would be returned
425
+ # for "dev.www.rubyonrails.org". You can specify a different `tld_length`, such
426
+ # as 2 to catch `["www"]` instead of `["www", "rubyonrails"]` in
427
+ # "www.rubyonrails.co.uk".
329
428
  def subdomains(tld_length = @@tld_length)
330
429
  ActionDispatch::Http::URL.extract_subdomains(host, tld_length)
331
430
  end
332
431
 
333
- # Returns all the \subdomains as a string, so <tt>"dev.www"</tt> would be
334
- # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
335
- # such as 2 to catch <tt>"www"</tt> instead of <tt>"www.rubyonrails"</tt>
336
- # in "www.rubyonrails.co.uk".
432
+ # Returns all the subdomains as a string, so `"dev.www"` would be returned for
433
+ # "dev.www.rubyonrails.org". You can specify a different `tld_length`, such as 2
434
+ # to catch `"www"` instead of `"www.rubyonrails"` in "www.rubyonrails.co.uk".
337
435
  def subdomain(tld_length = @@tld_length)
338
436
  ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
339
437
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_controller/metal/exceptions"
4
6
 
5
7
  module ActionDispatch
6
8
  # :stopdoc:
7
9
  module Journey
8
10
  # The Formatter class is used for formatting URLs. For example, parameters
9
- # passed to +url_for+ in Rails will eventually call Formatter#generate.
11
+ # passed to `url_for` in Rails will eventually call Formatter#generate.
10
12
  class Formatter
11
13
  attr_reader :routes
12
14
 
@@ -58,26 +60,31 @@ module ActionDispatch
58
60
 
59
61
  def generate(name, options, path_parameters)
60
62
  original_options = options.dup
61
- path_params = options.delete(:path_params) || {}
62
- options = path_params.merge(options)
63
+ path_params = options.delete(:path_params)
64
+ if path_params.is_a?(Hash)
65
+ options = path_params.merge(options)
66
+ else
67
+ path_params = nil
68
+ options = options.dup
69
+ end
63
70
  constraints = path_parameters.merge(options)
64
71
  missing_keys = nil
65
72
 
66
73
  match_route(name, constraints) do |route|
67
74
  parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
68
75
 
69
- # Skip this route unless a name has been provided or it is a
70
- # standard Rails route since we can't determine whether an options
71
- # hash passed to url_for matches a Rack application or a redirect.
76
+ # Skip this route unless a name has been provided or it is a standard Rails
77
+ # route since we can't determine whether an options hash passed to url_for
78
+ # matches a Rack application or a redirect.
72
79
  next unless name || route.dispatcher?
73
80
 
74
81
  missing_keys = missing_keys(route, parameterized_parts)
75
82
  next if missing_keys && !missing_keys.empty?
76
83
  params = options.delete_if do |key, _|
77
- # top-level params' normal behavior of generating query_params
78
- # should be preserved even if the same key is also a bind_param
84
+ # top-level params' normal behavior of generating query_params should be
85
+ # preserved even if the same key is also a bind_param
79
86
  parameterized_parts.key?(key) || route.defaults.key?(key) ||
80
- (path_params.key?(key) && !original_options.key?(key))
87
+ (path_params&.key?(key) && !original_options.key?(key))
81
88
  end
82
89
 
83
90
  defaults = route.defaults
@@ -104,6 +111,11 @@ module ActionDispatch
104
111
  @cache = nil
105
112
  end
106
113
 
114
+ def eager_load!
115
+ cache
116
+ nil
117
+ end
118
+
107
119
  private
108
120
  def extract_parameterized_parts(route, options, recall)
109
121
  parameterized_parts = recall.merge(options)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch/journey/gtg/transition_table"
4
6
 
5
7
  module ActionDispatch
@@ -66,9 +68,8 @@ module ActionDispatch
66
68
  when Nodes::Group
67
69
  true
68
70
  when Nodes::Star
69
- # the default star regex is /(.+)/ which is NOT nullable
70
- # but since different constraints can be provided we must
71
- # actually check if this is the case or not.
71
+ # the default star regex is /(.+)/ which is NOT nullable but since different
72
+ # constraints can be provided we must actually check if this is the case or not.
72
73
  node.regexp.match?("")
73
74
  when Nodes::Or
74
75
  node.children.any? { |c| nullable?(c) }
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "strscan"
3
+ # :markup: markdown
4
4
 
5
5
  module ActionDispatch
6
6
  module Journey # :nodoc:
@@ -14,7 +14,13 @@ module ActionDispatch
14
14
  end
15
15
 
16
16
  class Simulator # :nodoc:
17
- INITIAL_STATE = [ [0, nil] ].freeze
17
+ STATIC_TOKENS = Array.new(64)
18
+ STATIC_TOKENS[".".ord] = "."
19
+ STATIC_TOKENS["/".ord] = "/"
20
+ STATIC_TOKENS["?".ord] = "?"
21
+ STATIC_TOKENS.freeze
22
+
23
+ INITIAL_STATE = [0, nil].freeze
18
24
 
19
25
  attr_reader :tt
20
26
 
@@ -23,21 +29,38 @@ module ActionDispatch
23
29
  end
24
30
 
25
31
  def memos(string)
26
- input = StringScanner.new(string)
27
32
  state = INITIAL_STATE
28
- start_index = 0
29
33
 
30
- while sym = input.scan(%r([/.?]|[^/.?]+))
31
- end_index = start_index + sym.length
34
+ pos = 0
35
+ eos = string.bytesize
36
+
37
+ while pos < eos
38
+ start_index = pos
39
+ pos += 1
32
40
 
33
- state = tt.move(state, string, start_index, end_index)
41
+ if (token = STATIC_TOKENS[string.getbyte(start_index)])
42
+ state = tt.move(state, string, token, start_index, false)
43
+ else
44
+ while pos < eos && STATIC_TOKENS[string.getbyte(pos)].nil?
45
+ pos += 1
46
+ end
34
47
 
35
- start_index = end_index
48
+ token = string.byteslice(start_index, pos - start_index)
49
+ state = tt.move(state, string, token, start_index, true)
50
+ end
36
51
  end
37
52
 
38
- acceptance_states = state.each_with_object([]) do |s_d, memos|
39
- s, idx = s_d
40
- memos.concat(tt.memo(s)) if idx.nil? && tt.accepting?(s)
53
+ acceptance_states = []
54
+ states_count = state.size
55
+ i = 0
56
+ while i < states_count
57
+ if state[i + 1].nil?
58
+ s = state[i]
59
+ if tt.accepting?(s)
60
+ acceptance_states.concat(tt.memo(s))
61
+ end
62
+ end
63
+ i += 2
41
64
  end
42
65
 
43
66
  acceptance_states.empty? ? yield : acceptance_states