omg-actionpack 8.0.0.alpha1

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 (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +691 -0
  65. data/lib/action_controller.rb +80 -0
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,344 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/core_ext/module/attribute_accessors"
6
+
7
+ module ActionDispatch
8
+ module Http
9
+ module URL
10
+ IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
11
+ HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
12
+ PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
13
+
14
+ mattr_accessor :secure_protocol, default: false
15
+ mattr_accessor :tld_length, default: 1
16
+
17
+ class << self
18
+ # Returns the domain part of a host given the domain level.
19
+ #
20
+ # # Top-level domain example
21
+ # extract_domain('www.example.com', 1) # => "example.com"
22
+ # # Second-level domain example
23
+ # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
24
+ def extract_domain(host, tld_length)
25
+ extract_domain_from(host, tld_length) if named_host?(host)
26
+ end
27
+
28
+ # Returns the subdomains of a host as an Array given the domain level.
29
+ #
30
+ # # Top-level domain example
31
+ # extract_subdomains('www.example.com', 1) # => ["www"]
32
+ # # Second-level domain example
33
+ # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
34
+ def extract_subdomains(host, tld_length)
35
+ if named_host?(host)
36
+ extract_subdomains_from(host, tld_length)
37
+ else
38
+ []
39
+ end
40
+ end
41
+
42
+ # Returns the subdomains of a host as a String given the domain level.
43
+ #
44
+ # # Top-level domain example
45
+ # extract_subdomain('www.example.com', 1) # => "www"
46
+ # # Second-level domain example
47
+ # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
48
+ def extract_subdomain(host, tld_length)
49
+ extract_subdomains(host, tld_length).join(".")
50
+ end
51
+
52
+ def url_for(options)
53
+ if options[:only_path]
54
+ path_for options
55
+ else
56
+ full_url_for options
57
+ end
58
+ end
59
+
60
+ def full_url_for(options)
61
+ host = options[:host]
62
+ protocol = options[:protocol]
63
+ port = options[:port]
64
+
65
+ unless host
66
+ raise ArgumentError, "Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true"
67
+ end
68
+
69
+ build_host_url(host, port, protocol, options, path_for(options))
70
+ end
71
+
72
+ def path_for(options)
73
+ path = options[:script_name].to_s.chomp("/")
74
+ path << options[:path] if options.key?(:path)
75
+
76
+ path = "/" if options[:trailing_slash] && path.blank?
77
+
78
+ add_params(path, options[:params]) if options.key?(:params)
79
+ add_anchor(path, options[:anchor]) if options.key?(:anchor)
80
+
81
+ path
82
+ end
83
+
84
+ private
85
+ def add_params(path, params)
86
+ params = { params: params } unless params.is_a?(Hash)
87
+ params.reject! { |_, v| v.to_param.nil? }
88
+ query = params.to_query
89
+ path << "?#{query}" unless query.empty?
90
+ end
91
+
92
+ def add_anchor(path, anchor)
93
+ if anchor
94
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}"
95
+ end
96
+ end
97
+
98
+ def extract_domain_from(host, tld_length)
99
+ host.split(".").last(1 + tld_length).join(".")
100
+ end
101
+
102
+ def extract_subdomains_from(host, tld_length)
103
+ parts = host.split(".")
104
+ parts[0..-(tld_length + 2)]
105
+ end
106
+
107
+ def build_host_url(host, port, protocol, options, path)
108
+ if match = host.match(HOST_REGEXP)
109
+ protocol ||= match[1] unless protocol == false
110
+ host = match[2]
111
+ port = match[3] unless options.key? :port
112
+ end
113
+
114
+ protocol = normalize_protocol protocol
115
+ host = normalize_host(host, options)
116
+
117
+ result = protocol.dup
118
+
119
+ if options[:user] && options[:password]
120
+ result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
121
+ end
122
+
123
+ result << host
124
+ normalize_port(port, protocol) { |normalized_port|
125
+ result << ":#{normalized_port}"
126
+ }
127
+
128
+ result.concat path
129
+ end
130
+
131
+ def named_host?(host)
132
+ !IP_HOST_REGEXP.match?(host)
133
+ end
134
+
135
+ def normalize_protocol(protocol)
136
+ case protocol
137
+ when nil
138
+ secure_protocol ? "https://" : "http://"
139
+ when false, "//"
140
+ "//"
141
+ when PROTOCOL_REGEXP
142
+ "#{$1}://"
143
+ else
144
+ raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}"
145
+ end
146
+ end
147
+
148
+ def normalize_host(_host, options)
149
+ return _host unless named_host?(_host)
150
+
151
+ tld_length = options[:tld_length] || @@tld_length
152
+ subdomain = options.fetch :subdomain, true
153
+ domain = options[:domain]
154
+
155
+ host = +""
156
+ if subdomain == true
157
+ return _host if domain.nil?
158
+
159
+ host << extract_subdomains_from(_host, tld_length).join(".")
160
+ elsif subdomain
161
+ host << subdomain.to_param
162
+ end
163
+ host << "." unless host.empty?
164
+ host << (domain || extract_domain_from(_host, tld_length))
165
+ host
166
+ end
167
+
168
+ def normalize_port(port, protocol)
169
+ return unless port
170
+
171
+ case protocol
172
+ when "//" then yield port
173
+ when "https://"
174
+ yield port unless port.to_i == 443
175
+ else
176
+ yield port unless port.to_i == 80
177
+ end
178
+ end
179
+ end
180
+
181
+ def initialize
182
+ super
183
+ @protocol = nil
184
+ @port = nil
185
+ end
186
+
187
+ # Returns the complete URL used for this request.
188
+ #
189
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
190
+ # req.url # => "http://example.com"
191
+ def url
192
+ protocol + host_with_port + fullpath
193
+ end
194
+
195
+ # Returns 'https://' if this is an SSL request and 'http://' otherwise.
196
+ #
197
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
198
+ # req.protocol # => "http://"
199
+ #
200
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
201
+ # req.protocol # => "https://"
202
+ def protocol
203
+ @protocol ||= ssl? ? "https://" : "http://"
204
+ end
205
+
206
+ # Returns the host and port for this request, such as "example.com:8080".
207
+ #
208
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
209
+ # req.raw_host_with_port # => "example.com"
210
+ #
211
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
212
+ # req.raw_host_with_port # => "example.com:80"
213
+ #
214
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
215
+ # req.raw_host_with_port # => "example.com:8080"
216
+ def raw_host_with_port
217
+ if forwarded = x_forwarded_host.presence
218
+ forwarded.split(/,\s?/).last
219
+ else
220
+ get_header("HTTP_HOST") || "#{server_name}:#{get_header('SERVER_PORT')}"
221
+ end
222
+ end
223
+
224
+ # Returns the host for this request, such as "example.com".
225
+ #
226
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
227
+ # req.host # => "example.com"
228
+ def host
229
+ raw_host_with_port.sub(/:\d+$/, "")
230
+ end
231
+
232
+ # Returns a host:port string for this request, such as "example.com" or
233
+ # "example.com:8080". Port is only included if it is not a default port (80 or
234
+ # 443)
235
+ #
236
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
237
+ # req.host_with_port # => "example.com"
238
+ #
239
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
240
+ # req.host_with_port # => "example.com"
241
+ #
242
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
243
+ # req.host_with_port # => "example.com:8080"
244
+ def host_with_port
245
+ "#{host}#{port_string}"
246
+ end
247
+
248
+ # Returns the port number of this request as an integer.
249
+ #
250
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
251
+ # req.port # => 80
252
+ #
253
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
254
+ # req.port # => 8080
255
+ def port
256
+ @port ||= if raw_host_with_port =~ /:(\d+)$/
257
+ $1.to_i
258
+ else
259
+ standard_port
260
+ end
261
+ end
262
+
263
+ # Returns the standard port number for this request's protocol.
264
+ #
265
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
266
+ # req.standard_port # => 80
267
+ def standard_port
268
+ if "https://" == protocol
269
+ 443
270
+ else
271
+ 80
272
+ end
273
+ end
274
+
275
+ # Returns whether this request is using the standard port
276
+ #
277
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
278
+ # req.standard_port? # => true
279
+ #
280
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
281
+ # req.standard_port? # => false
282
+ def standard_port?
283
+ port == standard_port
284
+ end
285
+
286
+ # Returns a number port suffix like 8080 if the port number of this request is
287
+ # not the default HTTP port 80 or HTTPS port 443.
288
+ #
289
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
290
+ # req.optional_port # => nil
291
+ #
292
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
293
+ # req.optional_port # => 8080
294
+ def optional_port
295
+ standard_port? ? nil : port
296
+ end
297
+
298
+ # Returns a string port suffix, including colon, like ":8080" if the port number
299
+ # of this request is not the default HTTP port 80 or HTTPS port 443.
300
+ #
301
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
302
+ # req.port_string # => ""
303
+ #
304
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
305
+ # req.port_string # => ":8080"
306
+ def port_string
307
+ standard_port? ? "" : ":#{port}"
308
+ end
309
+
310
+ # Returns the requested port, such as 8080, based on SERVER_PORT
311
+ #
312
+ # req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
313
+ # req.server_port # => 80
314
+ #
315
+ # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080'
316
+ # req.server_port # => 8080
317
+ def server_port
318
+ get_header("SERVER_PORT").to_i
319
+ end
320
+
321
+ # Returns the domain part of a host, such as "rubyonrails.org" in
322
+ # "www.rubyonrails.org". You can specify a different `tld_length`, such as 2 to
323
+ # catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
324
+ def domain(tld_length = @@tld_length)
325
+ ActionDispatch::Http::URL.extract_domain(host, tld_length)
326
+ end
327
+
328
+ # Returns all the subdomains as an array, so `["dev", "www"]` would be returned
329
+ # for "dev.www.rubyonrails.org". You can specify a different `tld_length`, such
330
+ # as 2 to catch `["www"]` instead of `["www", "rubyonrails"]` in
331
+ # "www.rubyonrails.co.uk".
332
+ def subdomains(tld_length = @@tld_length)
333
+ ActionDispatch::Http::URL.extract_subdomains(host, tld_length)
334
+ end
335
+
336
+ # Returns all the subdomains as a string, so `"dev.www"` would be returned for
337
+ # "dev.www.rubyonrails.org". You can specify a different `tld_length`, such as 2
338
+ # to catch `"www"` instead of `"www.rubyonrails"` in "www.rubyonrails.co.uk".
339
+ def subdomain(tld_length = @@tld_length)
340
+ ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
341
+ end
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_controller/metal/exceptions"
6
+
7
+ module ActionDispatch
8
+ # :stopdoc:
9
+ module Journey
10
+ # The Formatter class is used for formatting URLs. For example, parameters
11
+ # passed to `url_for` in Rails will eventually call Formatter#generate.
12
+ class Formatter
13
+ attr_reader :routes
14
+
15
+ def initialize(routes)
16
+ @routes = routes
17
+ @cache = nil
18
+ end
19
+
20
+ class RouteWithParams
21
+ attr_reader :params
22
+
23
+ def initialize(route, parameterized_parts, params)
24
+ @route = route
25
+ @parameterized_parts = parameterized_parts
26
+ @params = params
27
+ end
28
+
29
+ def path(_)
30
+ @route.format(@parameterized_parts)
31
+ end
32
+ end
33
+
34
+ class MissingRoute
35
+ attr_reader :routes, :name, :constraints, :missing_keys, :unmatched_keys
36
+
37
+ def initialize(constraints, missing_keys, unmatched_keys, routes, name)
38
+ @constraints = constraints
39
+ @missing_keys = missing_keys
40
+ @unmatched_keys = unmatched_keys
41
+ @routes = routes
42
+ @name = name
43
+ end
44
+
45
+ def path(method_name)
46
+ raise ActionController::UrlGenerationError.new(message, routes, name, method_name)
47
+ end
48
+
49
+ def params
50
+ path("unknown")
51
+ end
52
+
53
+ def message
54
+ message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
55
+ message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
56
+ message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
57
+ message
58
+ end
59
+ end
60
+
61
+ def generate(name, options, path_parameters)
62
+ original_options = options.dup
63
+ path_params = options.delete(:path_params) || {}
64
+ options = path_params.merge(options)
65
+ constraints = path_parameters.merge(options)
66
+ missing_keys = nil
67
+
68
+ match_route(name, constraints) do |route|
69
+ parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
70
+
71
+ # Skip this route unless a name has been provided or it is a standard Rails
72
+ # route since we can't determine whether an options hash passed to url_for
73
+ # matches a Rack application or a redirect.
74
+ next unless name || route.dispatcher?
75
+
76
+ missing_keys = missing_keys(route, parameterized_parts)
77
+ next if missing_keys && !missing_keys.empty?
78
+ params = options.delete_if do |key, _|
79
+ # top-level params' normal behavior of generating query_params should be
80
+ # preserved even if the same key is also a bind_param
81
+ parameterized_parts.key?(key) || route.defaults.key?(key) ||
82
+ (path_params.key?(key) && !original_options.key?(key))
83
+ end
84
+
85
+ defaults = route.defaults
86
+ required_parts = route.required_parts
87
+
88
+ route.parts.reverse_each do |key|
89
+ break if defaults[key].nil? && parameterized_parts[key].present?
90
+ next if parameterized_parts[key].to_s != defaults[key].to_s
91
+ break if required_parts.include?(key)
92
+
93
+ parameterized_parts.delete(key)
94
+ end
95
+
96
+ return RouteWithParams.new(route, parameterized_parts, params)
97
+ end
98
+
99
+ unmatched_keys = (missing_keys || []) & constraints.keys
100
+ missing_keys = (missing_keys || []) - unmatched_keys
101
+
102
+ MissingRoute.new(constraints, missing_keys, unmatched_keys, routes, name)
103
+ end
104
+
105
+ def clear
106
+ @cache = nil
107
+ end
108
+
109
+ def eager_load!
110
+ cache
111
+ nil
112
+ end
113
+
114
+ private
115
+ def extract_parameterized_parts(route, options, recall)
116
+ parameterized_parts = recall.merge(options)
117
+
118
+ keys_to_keep = route.parts.reverse_each.drop_while { |part|
119
+ !(options.key?(part) || route.scope_options.key?(part)) || (options[part].nil? && recall[part].nil?)
120
+ } | route.required_parts
121
+
122
+ parameterized_parts.delete_if do |bad_key, _|
123
+ !keys_to_keep.include?(bad_key)
124
+ end
125
+
126
+ parameterized_parts.each do |k, v|
127
+ if k == :controller
128
+ parameterized_parts[k] = v
129
+ else
130
+ parameterized_parts[k] = v.to_param
131
+ end
132
+ end
133
+
134
+ parameterized_parts.compact!
135
+ parameterized_parts
136
+ end
137
+
138
+ def named_routes
139
+ routes.named_routes
140
+ end
141
+
142
+ def match_route(name, options)
143
+ if named_routes.key?(name)
144
+ yield named_routes[name]
145
+ else
146
+ routes = non_recursive(cache, options)
147
+
148
+ supplied_keys = options.each_with_object({}) do |(k, v), h|
149
+ h[k.to_s] = true if v
150
+ end
151
+
152
+ hash = routes.group_by { |_, r| r.score(supplied_keys) }
153
+
154
+ hash.keys.sort.reverse_each do |score|
155
+ break if score < 0
156
+
157
+ hash[score].sort_by { |i, _| i }.each do |_, route|
158
+ yield route
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def non_recursive(cache, options)
165
+ routes = []
166
+ queue = [cache]
167
+
168
+ while queue.any?
169
+ c = queue.shift
170
+ routes.concat(c[:___routes]) if c.key?(:___routes)
171
+
172
+ options.each do |pair|
173
+ queue << c[pair] if c.key?(pair)
174
+ end
175
+ end
176
+
177
+ routes
178
+ end
179
+
180
+ # Returns an array populated with missing keys if any are present.
181
+ def missing_keys(route, parts)
182
+ missing_keys = nil
183
+ tests = route.path.requirements_for_missing_keys_check
184
+ route.required_parts.each { |key|
185
+ case tests[key]
186
+ when nil
187
+ unless parts[key]
188
+ missing_keys ||= []
189
+ missing_keys << key
190
+ end
191
+ else
192
+ unless tests[key].match?(parts[key])
193
+ missing_keys ||= []
194
+ missing_keys << key
195
+ end
196
+ end
197
+ }
198
+ missing_keys
199
+ end
200
+
201
+ def possibles(cache, options, depth = 0)
202
+ cache.fetch(:___routes) { [] } + options.find_all { |pair|
203
+ cache.key?(pair)
204
+ }.flat_map { |pair|
205
+ possibles(cache[pair], options, depth + 1)
206
+ }
207
+ end
208
+
209
+ def build_cache
210
+ root = { ___routes: [] }
211
+ routes.routes.each_with_index do |route, i|
212
+ leaf = route.required_defaults.inject(root) do |h, tuple|
213
+ h[tuple] ||= {}
214
+ end
215
+ (leaf[:___routes] ||= []) << [i, route]
216
+ end
217
+ root
218
+ end
219
+
220
+ def cache
221
+ @cache ||= build_cache
222
+ end
223
+ end
224
+ end
225
+ # :startdoc:
226
+ end