omg-actionpack 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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