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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +429 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller.rb +27 -0
- data/lib/abstract_controller/asset_paths.rb +12 -0
- data/lib/abstract_controller/base.rb +265 -0
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/caching/fragments.rb +166 -0
- data/lib/abstract_controller/callbacks.rb +212 -0
- data/lib/abstract_controller/collector.rb +43 -0
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +194 -0
- data/lib/abstract_controller/logger.rb +14 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
- data/lib/abstract_controller/rendering.rb +127 -0
- data/lib/abstract_controller/translation.rb +31 -0
- data/lib/abstract_controller/url_for.rb +35 -0
- data/lib/action_controller.rb +66 -0
- data/lib/action_controller/api.rb +149 -0
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/base.rb +276 -0
- data/lib/action_controller/caching.rb +46 -0
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +78 -0
- data/lib/action_controller/metal.rb +256 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +274 -0
- data/lib/action_controller/metal/content_security_policy.rb +52 -0
- data/lib/action_controller/metal/cookies.rb +16 -0
- data/lib/action_controller/metal/data_streaming.rb +152 -0
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
- data/lib/action_controller/metal/exceptions.rb +53 -0
- data/lib/action_controller/metal/flash.rb +61 -0
- data/lib/action_controller/metal/force_ssl.rb +99 -0
- data/lib/action_controller/metal/head.rb +60 -0
- data/lib/action_controller/metal/helpers.rb +123 -0
- data/lib/action_controller/metal/http_authentication.rb +519 -0
- data/lib/action_controller/metal/implicit_render.rb +73 -0
- data/lib/action_controller/metal/instrumentation.rb +107 -0
- data/lib/action_controller/metal/live.rb +312 -0
- data/lib/action_controller/metal/mime_responds.rb +313 -0
- data/lib/action_controller/metal/parameter_encoding.rb +51 -0
- data/lib/action_controller/metal/params_wrapper.rb +293 -0
- data/lib/action_controller/metal/redirecting.rb +133 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +122 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +445 -0
- data/lib/action_controller/metal/rescue.rb +28 -0
- data/lib/action_controller/metal/streaming.rb +223 -0
- data/lib/action_controller/metal/strong_parameters.rb +1086 -0
- data/lib/action_controller/metal/testing.rb +16 -0
- data/lib/action_controller/metal/url_for.rb +58 -0
- data/lib/action_controller/railtie.rb +89 -0
- data/lib/action_controller/railties/helpers.rb +24 -0
- data/lib/action_controller/renderer.rb +117 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +629 -0
- data/lib/action_dispatch.rb +112 -0
- data/lib/action_dispatch/http/cache.rb +222 -0
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +84 -0
- data/lib/action_dispatch/http/filter_redirect.rb +37 -0
- data/lib/action_dispatch/http/headers.rb +132 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +175 -0
- data/lib/action_dispatch/http/mime_type.rb +342 -0
- data/lib/action_dispatch/http/mime_types.rb +50 -0
- data/lib/action_dispatch/http/parameter_filter.rb +86 -0
- data/lib/action_dispatch/http/parameters.rb +126 -0
- data/lib/action_dispatch/http/rack_cache.rb +63 -0
- data/lib/action_dispatch/http/request.rb +430 -0
- data/lib/action_dispatch/http/response.rb +519 -0
- data/lib/action_dispatch/http/upload.rb +84 -0
- data/lib/action_dispatch/http/url.rb +350 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/journey/formatter.rb +189 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
- data/lib/action_dispatch/journey/nodes/node.rb +140 -0
- data/lib/action_dispatch/journey/parser.rb +199 -0
- data/lib/action_dispatch/journey/parser.y +50 -0
- data/lib/action_dispatch/journey/parser_extras.rb +31 -0
- data/lib/action_dispatch/journey/path/pattern.rb +198 -0
- data/lib/action_dispatch/journey/route.rb +203 -0
- data/lib/action_dispatch/journey/router.rb +156 -0
- data/lib/action_dispatch/journey/router/utils.rb +102 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +64 -0
- data/lib/action_dispatch/journey/visitors.rb +268 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/middleware/callbacks.rb +36 -0
- data/lib/action_dispatch/middleware/cookies.rb +685 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +205 -0
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +147 -0
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +300 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +57 -0
- data/lib/action_dispatch/middleware/reloader.rb +12 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +183 -0
- data/lib/action_dispatch/middleware/request_id.rb +43 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +118 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
- data/lib/action_dispatch/middleware/ssl.rb +150 -0
- data/lib/action_dispatch/middleware/stack.rb +116 -0
- data/lib/action_dispatch/middleware/static.rb +130 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +161 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
- data/lib/action_dispatch/railtie.rb +55 -0
- data/lib/action_dispatch/request/session.rb +234 -0
- data/lib/action_dispatch/request/utils.rb +78 -0
- data/lib/action_dispatch/routing.rb +260 -0
- data/lib/action_dispatch/routing/endpoint.rb +17 -0
- data/lib/action_dispatch/routing/inspector.rb +225 -0
- data/lib/action_dispatch/routing/mapper.rb +2267 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +352 -0
- data/lib/action_dispatch/routing/redirection.rb +201 -0
- data/lib/action_dispatch/routing/route_set.rb +890 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
- data/lib/action_dispatch/routing/url_for.rb +236 -0
- data/lib/action_dispatch/system_test_case.rb +147 -0
- data/lib/action_dispatch/system_testing/browser.rb +49 -0
- data/lib/action_dispatch/system_testing/driver.rb +59 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +47 -0
- data/lib/action_dispatch/testing/assertions.rb +24 -0
- data/lib/action_dispatch/testing/assertions/response.rb +107 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +222 -0
- data/lib/action_dispatch/testing/integration.rb +652 -0
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +50 -0
- data/lib/action_dispatch/testing/test_request.rb +71 -0
- data/lib/action_dispatch/testing/test_response.rb +53 -0
- data/lib/action_pack.rb +26 -0
- data/lib/action_pack/gem_version.rb +17 -0
- data/lib/action_pack/version.rb +10 -0
- 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
|