actionpack 5.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

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