actionpack 6.0.5.1 → 6.1.0
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 +4 -4
- data/CHANGELOG.md +248 -344
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/abstract_controller/base.rb +35 -2
- data/lib/abstract_controller/callbacks.rb +2 -2
- data/lib/abstract_controller/helpers.rb +105 -90
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +8 -2
- data/lib/abstract_controller.rb +1 -0
- data/lib/action_controller/api.rb +2 -2
- data/lib/action_controller/base.rb +4 -2
- data/lib/action_controller/caching.rb +0 -1
- data/lib/action_controller/log_subscriber.rb +3 -3
- data/lib/action_controller/metal/conditional_get.rb +10 -2
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/cookies.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +1 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
- data/lib/action_controller/metal/exceptions.rb +33 -0
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +11 -1
- data/lib/action_controller/metal/http_authentication.rb +5 -3
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +11 -9
- data/lib/action_controller/metal/live.rb +1 -1
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +6 -2
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +16 -11
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +1 -1
- data/lib/action_controller/metal/rendering.rb +6 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +1 -1
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +103 -15
- data/lib/action_controller/metal.rb +2 -2
- data/lib/action_controller/renderer.rb +23 -13
- data/lib/action_controller/test_case.rb +62 -56
- data/lib/action_controller.rb +2 -3
- data/lib/action_dispatch/http/cache.rb +12 -10
- data/lib/action_dispatch/http/content_security_policy.rb +11 -0
- data/lib/action_dispatch/http/filter_parameters.rb +1 -1
- data/lib/action_dispatch/http/filter_redirect.rb +1 -1
- data/lib/action_dispatch/http/headers.rb +3 -2
- data/lib/action_dispatch/http/mime_negotiation.rb +14 -8
- data/lib/action_dispatch/http/mime_type.rb +29 -16
- data/lib/action_dispatch/http/parameters.rb +1 -19
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +24 -8
- data/lib/action_dispatch/http/response.rb +17 -16
- data/lib/action_dispatch/http/url.rb +3 -2
- data/lib/action_dispatch/journey/formatter.rb +53 -28
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +4 -3
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +13 -18
- data/lib/action_dispatch/journey/route.rb +7 -18
- data/lib/action_dispatch/journey/router/utils.rb +6 -4
- data/lib/action_dispatch/journey/router.rb +26 -30
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +1 -1
- data/lib/action_dispatch/middleware/cookies.rb +67 -32
- data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -15
- data/lib/action_dispatch/middleware/debug_view.rb +1 -1
- data/lib/action_dispatch/middleware/exception_wrapper.rb +28 -16
- data/lib/action_dispatch/middleware/executor.rb +1 -1
- data/lib/action_dispatch/middleware/host_authorization.rb +35 -35
- data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
- data/lib/action_dispatch/middleware/request_id.rb +4 -5
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
- data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
- data/lib/action_dispatch/middleware/ssl.rb +9 -6
- data/lib/action_dispatch/middleware/stack.rb +18 -0
- data/lib/action_dispatch/middleware/static.rb +154 -93
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +100 -8
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
- data/lib/action_dispatch/railtie.rb +3 -2
- data/lib/action_dispatch/request/session.rb +2 -8
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing/inspector.rb +8 -7
- data/lib/action_dispatch/routing/mapper.rb +102 -71
- data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -19
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +49 -41
- data/lib/action_dispatch/system_test_case.rb +29 -24
- data/lib/action_dispatch/system_testing/browser.rb +33 -27
- data/lib/action_dispatch/system_testing/driver.rb +6 -7
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
- data/lib/action_dispatch/testing/assertions/response.rb +2 -4
- data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +38 -27
- data/lib/action_dispatch/testing/test_process.rb +29 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch.rb +3 -2
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +21 -23
- data/lib/action_controller/metal/force_ssl.rb +0 -58
- data/lib/action_dispatch/http/parameter_filter.rb +0 -12
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/deep_dup"
|
4
|
+
|
5
|
+
module ActionDispatch #:nodoc:
|
6
|
+
class PermissionsPolicy
|
7
|
+
class Middleware
|
8
|
+
CONTENT_TYPE = "Content-Type"
|
9
|
+
# The Feature-Policy header has been renamed to Permissions-Policy.
|
10
|
+
# The Permissions-Policy requires a different implementation and isn't
|
11
|
+
# yet supported by all browsers. To avoid having to rename this
|
12
|
+
# middleware in the future we use the new name for the middleware but
|
13
|
+
# keep the old header name and implementation for now.
|
14
|
+
POLICY = "Feature-Policy"
|
15
|
+
|
16
|
+
def initialize(app)
|
17
|
+
@app = app
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
request = ActionDispatch::Request.new(env)
|
22
|
+
_, headers, _ = response = @app.call(env)
|
23
|
+
|
24
|
+
return response unless html_response?(headers)
|
25
|
+
return response if policy_present?(headers)
|
26
|
+
|
27
|
+
if policy = request.permissions_policy
|
28
|
+
headers[POLICY] = policy.build(request.controller_instance)
|
29
|
+
end
|
30
|
+
|
31
|
+
if policy_empty?(policy)
|
32
|
+
headers.delete(POLICY)
|
33
|
+
end
|
34
|
+
|
35
|
+
response
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def html_response?(headers)
|
40
|
+
if content_type = headers[CONTENT_TYPE]
|
41
|
+
/html/.match?(content_type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def policy_present?(headers)
|
46
|
+
headers[POLICY]
|
47
|
+
end
|
48
|
+
|
49
|
+
def policy_empty?(policy)
|
50
|
+
policy&.directives&.empty?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module Request
|
55
|
+
POLICY = "action_dispatch.permissions_policy"
|
56
|
+
|
57
|
+
def permissions_policy
|
58
|
+
get_header(POLICY)
|
59
|
+
end
|
60
|
+
|
61
|
+
def permissions_policy=(policy)
|
62
|
+
set_header(POLICY, policy)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
MAPPINGS = {
|
67
|
+
self: "'self'",
|
68
|
+
none: "'none'",
|
69
|
+
}.freeze
|
70
|
+
|
71
|
+
# List of available permissions can be found at
|
72
|
+
# https://github.com/w3c/webappsec-permissions-policy/blob/master/features.md#policy-controlled-features
|
73
|
+
DIRECTIVES = {
|
74
|
+
accelerometer: "accelerometer",
|
75
|
+
ambient_light_sensor: "ambient-light-sensor",
|
76
|
+
autoplay: "autoplay",
|
77
|
+
camera: "camera",
|
78
|
+
encrypted_media: "encrypted-media",
|
79
|
+
fullscreen: "fullscreen",
|
80
|
+
geolocation: "geolocation",
|
81
|
+
gyroscope: "gyroscope",
|
82
|
+
magnetometer: "magnetometer",
|
83
|
+
microphone: "microphone",
|
84
|
+
midi: "midi",
|
85
|
+
payment: "payment",
|
86
|
+
picture_in_picture: "picture-in-picture",
|
87
|
+
speaker: "speaker",
|
88
|
+
usb: "usb",
|
89
|
+
vibrate: "vibrate",
|
90
|
+
vr: "vr",
|
91
|
+
}.freeze
|
92
|
+
|
93
|
+
private_constant :MAPPINGS, :DIRECTIVES
|
94
|
+
|
95
|
+
attr_reader :directives
|
96
|
+
|
97
|
+
def initialize
|
98
|
+
@directives = {}
|
99
|
+
yield self if block_given?
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize_copy(other)
|
103
|
+
@directives = other.directives.deep_dup
|
104
|
+
end
|
105
|
+
|
106
|
+
DIRECTIVES.each do |name, directive|
|
107
|
+
define_method(name) do |*sources|
|
108
|
+
if sources.first
|
109
|
+
@directives[directive] = apply_mappings(sources)
|
110
|
+
else
|
111
|
+
@directives.delete(directive)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def build(context = nil)
|
117
|
+
build_directives(context).compact.join("; ")
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
def apply_mappings(sources)
|
122
|
+
sources.map do |source|
|
123
|
+
case source
|
124
|
+
when Symbol
|
125
|
+
apply_mapping(source)
|
126
|
+
when String, Proc
|
127
|
+
source
|
128
|
+
else
|
129
|
+
raise ArgumentError, "Invalid HTTP permissions policy source: #{source.inspect}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def apply_mapping(source)
|
135
|
+
MAPPINGS.fetch(source) do
|
136
|
+
raise ArgumentError, "Unknown HTTP permissions policy source mapping: #{source.inspect}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def build_directives(context)
|
141
|
+
@directives.map do |directive, sources|
|
142
|
+
if sources.is_a?(Array)
|
143
|
+
"#{directive} #{build_directive(sources, context).join(' ')}"
|
144
|
+
elsif sources
|
145
|
+
directive
|
146
|
+
else
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def build_directive(sources, context)
|
153
|
+
sources.map { |source| resolve_source(source, context) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def resolve_source(source, context)
|
157
|
+
case source
|
158
|
+
when String
|
159
|
+
source
|
160
|
+
when Symbol
|
161
|
+
source.to_s
|
162
|
+
when Proc
|
163
|
+
if context.nil?
|
164
|
+
raise RuntimeError, "Missing context for the dynamic permissions policy source: #{source.inspect}"
|
165
|
+
else
|
166
|
+
context.instance_exec(&source)
|
167
|
+
end
|
168
|
+
else
|
169
|
+
raise RuntimeError, "Unexpected permissions policy source: #{source.inspect}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -23,6 +23,7 @@ module ActionDispatch
|
|
23
23
|
include ActionDispatch::Http::FilterParameters
|
24
24
|
include ActionDispatch::Http::URL
|
25
25
|
include ActionDispatch::ContentSecurityPolicy::Request
|
26
|
+
include ActionDispatch::PermissionsPolicy::Request
|
26
27
|
include Rack::Request::Env
|
27
28
|
|
28
29
|
autoload :Session, "action_dispatch/request/session"
|
@@ -44,11 +45,14 @@ module ActionDispatch
|
|
44
45
|
SERVER_ADDR
|
45
46
|
].freeze
|
46
47
|
|
48
|
+
# TODO: Remove SERVER_ADDR when we remove support to Rack 2.1.
|
49
|
+
# See https://github.com/rack/rack/commit/c173b188d81ee437b588c1e046a1c9f031dea550
|
47
50
|
ENV_METHODS.each do |env|
|
48
51
|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
# frozen_string_literal: true
|
53
|
+
def #{env.delete_prefix("HTTP_").downcase} # def accept_charset
|
54
|
+
get_header "#{env}" # get_header "HTTP_ACCEPT_CHARSET"
|
55
|
+
end # end
|
52
56
|
METHOD
|
53
57
|
end
|
54
58
|
|
@@ -72,7 +76,7 @@ module ActionDispatch
|
|
72
76
|
PASS_NOT_FOUND = Class.new { # :nodoc:
|
73
77
|
def self.action(_); self; end
|
74
78
|
def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end
|
75
|
-
def self.
|
79
|
+
def self.action_encoding_template(action); false; end
|
76
80
|
}
|
77
81
|
|
78
82
|
def controller_class
|
@@ -84,7 +88,7 @@ module ActionDispatch
|
|
84
88
|
def controller_class_for(name)
|
85
89
|
if name
|
86
90
|
controller_param = name.underscore
|
87
|
-
const_name =
|
91
|
+
const_name = controller_param.camelize << "Controller"
|
88
92
|
begin
|
89
93
|
ActiveSupport::Dependencies.constantize(const_name)
|
90
94
|
rescue NameError => error
|
@@ -274,7 +278,7 @@ module ActionDispatch
|
|
274
278
|
# (case-insensitive), which may need to be manually added depending on the
|
275
279
|
# choice of JavaScript libraries and frameworks.
|
276
280
|
def xml_http_request?
|
277
|
-
get_header("HTTP_X_REQUESTED_WITH")
|
281
|
+
/XMLHttpRequest/i.match?(get_header("HTTP_X_REQUESTED_WITH"))
|
278
282
|
end
|
279
283
|
alias :xhr? :xml_http_request?
|
280
284
|
|
@@ -290,6 +294,7 @@ module ActionDispatch
|
|
290
294
|
end
|
291
295
|
|
292
296
|
def remote_ip=(remote_ip)
|
297
|
+
@remote_ip = nil
|
293
298
|
set_header "action_dispatch.remote_ip", remote_ip
|
294
299
|
end
|
295
300
|
|
@@ -331,7 +336,7 @@ module ActionDispatch
|
|
331
336
|
# variable is already set, wrap it in a StringIO.
|
332
337
|
def body
|
333
338
|
if raw_post = get_header("RAW_POST_DATA")
|
334
|
-
raw_post = raw_post.
|
339
|
+
raw_post = (+raw_post).force_encoding(Encoding::BINARY)
|
335
340
|
StringIO.new(raw_post)
|
336
341
|
else
|
337
342
|
body_stream
|
@@ -376,6 +381,9 @@ module ActionDispatch
|
|
376
381
|
def GET
|
377
382
|
fetch_header("action_dispatch.request.query_parameters") do |k|
|
378
383
|
rack_query_params = super || {}
|
384
|
+
controller = path_parameters[:controller]
|
385
|
+
action = path_parameters[:action]
|
386
|
+
rack_query_params = Request::Utils.set_binary_encoding(self, rack_query_params, controller, action)
|
379
387
|
# Check for non UTF-8 parameter values, which would cause errors later
|
380
388
|
Request::Utils.check_param_encoding(rack_query_params)
|
381
389
|
set_header k, Request::Utils.normalize_encode_params(rack_query_params)
|
@@ -391,6 +399,8 @@ module ActionDispatch
|
|
391
399
|
pr = parse_formatted_parameters(params_parsers) do |params|
|
392
400
|
super || {}
|
393
401
|
end
|
402
|
+
pr = Request::Utils.set_binary_encoding(self, pr, path_parameters[:controller], path_parameters[:action])
|
403
|
+
Request::Utils.check_param_encoding(pr)
|
394
404
|
self.request_parameters = Request::Utils.normalize_encode_params(pr)
|
395
405
|
end
|
396
406
|
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
|
@@ -409,7 +419,7 @@ module ActionDispatch
|
|
409
419
|
|
410
420
|
# True if the request came from localhost, 127.0.0.1, or ::1.
|
411
421
|
def local?
|
412
|
-
LOCALHOST
|
422
|
+
LOCALHOST.match?(remote_addr) && LOCALHOST.match?(remote_ip)
|
413
423
|
end
|
414
424
|
|
415
425
|
def request_parameters=(params)
|
@@ -428,6 +438,10 @@ module ActionDispatch
|
|
428
438
|
super || scheme == "wss"
|
429
439
|
end
|
430
440
|
|
441
|
+
def inspect # :nodoc:
|
442
|
+
"#<#{self.class.name} #{method} #{original_url.dump} for #{remote_ip}>"
|
443
|
+
end
|
444
|
+
|
431
445
|
private
|
432
446
|
def check_method(name)
|
433
447
|
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
|
@@ -435,3 +449,5 @@ module ActionDispatch
|
|
435
449
|
end
|
436
450
|
end
|
437
451
|
end
|
452
|
+
|
453
|
+
ActiveSupport.run_load_hooks :action_dispatch_request, ActionDispatch::Request
|
@@ -81,11 +81,22 @@ module ActionDispatch # :nodoc:
|
|
81
81
|
CONTENT_TYPE = "Content-Type"
|
82
82
|
SET_COOKIE = "Set-Cookie"
|
83
83
|
LOCATION = "Location"
|
84
|
-
NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
|
84
|
+
NO_CONTENT_CODES = [100, 101, 102, 103, 204, 205, 304]
|
85
85
|
|
86
86
|
cattr_accessor :default_charset, default: "utf-8"
|
87
87
|
cattr_accessor :default_headers
|
88
|
-
|
88
|
+
|
89
|
+
def self.return_only_media_type_on_content_type=(*)
|
90
|
+
ActiveSupport::Deprecation.warn(
|
91
|
+
".return_only_media_type_on_content_type= is dreprecated with no replacement and will be removed in 6.2."
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.return_only_media_type_on_content_type
|
96
|
+
ActiveSupport::Deprecation.warn(
|
97
|
+
".return_only_media_type_on_content_type is dreprecated with no replacement and will be removed in 6.2."
|
98
|
+
)
|
99
|
+
end
|
89
100
|
|
90
101
|
include Rack::Response::Helpers
|
91
102
|
# Aliasing these off because AD::Http::Cache::Response defines them.
|
@@ -243,17 +254,7 @@ module ActionDispatch # :nodoc:
|
|
243
254
|
|
244
255
|
# Content type of response.
|
245
256
|
def content_type
|
246
|
-
|
247
|
-
ActiveSupport::Deprecation.warn(
|
248
|
-
"Rails 6.1 will return Content-Type header without modification." \
|
249
|
-
" If you want just the MIME type, please use `#media_type` instead."
|
250
|
-
)
|
251
|
-
|
252
|
-
content_type = super
|
253
|
-
content_type ? content_type.split(/;\s*charset=/)[0].presence : content_type
|
254
|
-
else
|
255
|
-
super.presence
|
256
|
-
end
|
257
|
+
super.presence
|
257
258
|
end
|
258
259
|
|
259
260
|
# Media type of response.
|
@@ -442,8 +443,8 @@ module ActionDispatch # :nodoc:
|
|
442
443
|
end
|
443
444
|
|
444
445
|
def set_content_type(content_type, charset)
|
445
|
-
type =
|
446
|
-
type
|
446
|
+
type = content_type || ""
|
447
|
+
type = "#{type}; charset=#{charset.to_s.downcase}" if charset
|
447
448
|
set_header CONTENT_TYPE, type
|
448
449
|
end
|
449
450
|
|
@@ -503,7 +504,7 @@ module ActionDispatch # :nodoc:
|
|
503
504
|
end
|
504
505
|
|
505
506
|
def respond_to?(method, include_private = false)
|
506
|
-
if method.
|
507
|
+
if method.to_sym == :to_path
|
507
508
|
@response.stream.respond_to?(method)
|
508
509
|
else
|
509
510
|
super
|
@@ -9,6 +9,7 @@ module ActionDispatch
|
|
9
9
|
HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
|
10
10
|
PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
|
11
11
|
|
12
|
+
mattr_accessor :secure_protocol, default: false
|
12
13
|
mattr_accessor :tld_length, default: 1
|
13
14
|
|
14
15
|
class << self
|
@@ -133,13 +134,13 @@ module ActionDispatch
|
|
133
134
|
end
|
134
135
|
|
135
136
|
def named_host?(host)
|
136
|
-
IP_HOST_REGEXP
|
137
|
+
!IP_HOST_REGEXP.match?(host)
|
137
138
|
end
|
138
139
|
|
139
140
|
def normalize_protocol(protocol)
|
140
141
|
case protocol
|
141
142
|
when nil
|
142
|
-
"http://"
|
143
|
+
secure_protocol ? "https://" : "http://"
|
143
144
|
when false, "//"
|
144
145
|
"//"
|
145
146
|
when PROTOCOL_REGEXP
|
@@ -15,12 +15,53 @@ module ActionDispatch
|
|
15
15
|
@cache = nil
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
class RouteWithParams
|
19
|
+
attr_reader :params
|
20
|
+
|
21
|
+
def initialize(route, parameterized_parts, params)
|
22
|
+
@route = route
|
23
|
+
@parameterized_parts = parameterized_parts
|
24
|
+
@params = params
|
25
|
+
end
|
26
|
+
|
27
|
+
def path(_)
|
28
|
+
@route.format(@parameterized_parts)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class MissingRoute
|
33
|
+
attr_reader :routes, :name, :constraints, :missing_keys, :unmatched_keys
|
34
|
+
|
35
|
+
def initialize(constraints, missing_keys, unmatched_keys, routes, name)
|
36
|
+
@constraints = constraints
|
37
|
+
@missing_keys = missing_keys
|
38
|
+
@unmatched_keys = unmatched_keys
|
39
|
+
@routes = routes
|
40
|
+
@name = name
|
41
|
+
end
|
42
|
+
|
43
|
+
def path(method_name)
|
44
|
+
raise ActionController::UrlGenerationError.new(message, routes, name, method_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def params
|
48
|
+
path("unknown")
|
49
|
+
end
|
50
|
+
|
51
|
+
def message
|
52
|
+
message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
|
53
|
+
message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
|
54
|
+
message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
|
55
|
+
message
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate(name, options, path_parameters)
|
19
60
|
constraints = path_parameters.merge(options)
|
20
61
|
missing_keys = nil
|
21
62
|
|
22
63
|
match_route(name, constraints) do |route|
|
23
|
-
parameterized_parts = extract_parameterized_parts(route, options, path_parameters
|
64
|
+
parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
|
24
65
|
|
25
66
|
# Skip this route unless a name has been provided or it is a
|
26
67
|
# standard Rails route since we can't determine whether an options
|
@@ -44,17 +85,13 @@ module ActionDispatch
|
|
44
85
|
parameterized_parts.delete(key)
|
45
86
|
end
|
46
87
|
|
47
|
-
return
|
88
|
+
return RouteWithParams.new(route, parameterized_parts, params)
|
48
89
|
end
|
49
90
|
|
50
91
|
unmatched_keys = (missing_keys || []) & constraints.keys
|
51
92
|
missing_keys = (missing_keys || []) - unmatched_keys
|
52
93
|
|
53
|
-
|
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
|
94
|
+
MissingRoute.new(constraints, missing_keys, unmatched_keys, routes, name)
|
58
95
|
end
|
59
96
|
|
60
97
|
def clear
|
@@ -62,7 +99,7 @@ module ActionDispatch
|
|
62
99
|
end
|
63
100
|
|
64
101
|
private
|
65
|
-
def extract_parameterized_parts(route, options, recall
|
102
|
+
def extract_parameterized_parts(route, options, recall)
|
66
103
|
parameterized_parts = recall.merge(options)
|
67
104
|
|
68
105
|
keys_to_keep = route.parts.reverse_each.drop_while { |part|
|
@@ -73,9 +110,11 @@ module ActionDispatch
|
|
73
110
|
!keys_to_keep.include?(bad_key)
|
74
111
|
end
|
75
112
|
|
76
|
-
|
77
|
-
|
78
|
-
parameterized_parts[k] =
|
113
|
+
parameterized_parts.each do |k, v|
|
114
|
+
if k == :controller
|
115
|
+
parameterized_parts[k] = v
|
116
|
+
else
|
117
|
+
parameterized_parts[k] = v.to_param
|
79
118
|
end
|
80
119
|
end
|
81
120
|
|
@@ -125,19 +164,10 @@ module ActionDispatch
|
|
125
164
|
routes
|
126
165
|
end
|
127
166
|
|
128
|
-
module RegexCaseComparator
|
129
|
-
DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/
|
130
|
-
DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/
|
131
|
-
|
132
|
-
def self.===(regex)
|
133
|
-
DEFAULT_INPUT == regex
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
167
|
# Returns an array populated with missing keys if any are present.
|
138
168
|
def missing_keys(route, parts)
|
139
169
|
missing_keys = nil
|
140
|
-
tests = route.path.
|
170
|
+
tests = route.path.requirements_for_missing_keys_check
|
141
171
|
route.required_parts.each { |key|
|
142
172
|
case tests[key]
|
143
173
|
when nil
|
@@ -145,13 +175,8 @@ module ActionDispatch
|
|
145
175
|
missing_keys ||= []
|
146
176
|
missing_keys << key
|
147
177
|
end
|
148
|
-
when RegexCaseComparator
|
149
|
-
unless RegexCaseComparator::DEFAULT_REGEX === parts[key]
|
150
|
-
missing_keys ||= []
|
151
|
-
missing_keys << key
|
152
|
-
end
|
153
178
|
else
|
154
|
-
unless
|
179
|
+
unless tests[key].match?(parts[key])
|
155
180
|
missing_keys ||= []
|
156
181
|
missing_keys << key
|
157
182
|
end
|
@@ -13,45 +13,44 @@ module ActionDispatch
|
|
13
13
|
def initialize(root)
|
14
14
|
@root = root
|
15
15
|
@ast = Nodes::Cat.new root, DUMMY
|
16
|
-
@followpos =
|
16
|
+
@followpos = build_followpos
|
17
17
|
end
|
18
18
|
|
19
19
|
def transition_table
|
20
20
|
dtrans = TransitionTable.new
|
21
|
-
marked = {}
|
22
|
-
state_id = Hash.new { |h, k| h[k] = h.length }
|
21
|
+
marked = {}.compare_by_identity
|
22
|
+
state_id = Hash.new { |h, k| h[k] = h.length }.compare_by_identity
|
23
|
+
dstates = [firstpos(root)]
|
23
24
|
|
24
|
-
start = firstpos(root)
|
25
|
-
dstates = [start]
|
26
25
|
until dstates.empty?
|
27
26
|
s = dstates.shift
|
28
27
|
next if marked[s]
|
29
28
|
marked[s] = true # mark s
|
30
29
|
|
31
30
|
s.group_by { |state| symbol(state) }.each do |sym, ps|
|
32
|
-
u = ps.flat_map { |l| followpos
|
31
|
+
u = ps.flat_map { |l| @followpos[l] }
|
33
32
|
next if u.empty?
|
34
33
|
|
35
|
-
|
36
|
-
from = state_id[s]
|
37
|
-
to = state_id[Object.new]
|
38
|
-
dtrans[from, to] = sym
|
34
|
+
from = state_id[s]
|
39
35
|
|
36
|
+
if u.all? { |pos| pos == DUMMY }
|
37
|
+
to = state_id[Object.new]
|
38
|
+
dtrans[from, to] = sym
|
40
39
|
dtrans.add_accepting(to)
|
40
|
+
|
41
41
|
ps.each { |state| dtrans.add_memo(to, state.memo) }
|
42
42
|
else
|
43
|
-
|
43
|
+
to = state_id[u]
|
44
|
+
dtrans[from, to] = sym
|
44
45
|
|
45
46
|
if u.include?(DUMMY)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
dtrans.add_memo(to, accepting_state.memo)
|
52
|
-
}
|
47
|
+
ps.each do |state|
|
48
|
+
if @followpos[state].include?(DUMMY)
|
49
|
+
dtrans.add_memo(to, state.memo)
|
50
|
+
end
|
51
|
+
end
|
53
52
|
|
54
|
-
dtrans.add_accepting(
|
53
|
+
dtrans.add_accepting(to)
|
55
54
|
end
|
56
55
|
end
|
57
56
|
|
@@ -92,7 +91,7 @@ module ActionDispatch
|
|
92
91
|
firstpos(node.left)
|
93
92
|
end
|
94
93
|
when Nodes::Or
|
95
|
-
node.children.flat_map { |c| firstpos(c) }.uniq
|
94
|
+
node.children.flat_map { |c| firstpos(c) }.tap(&:uniq!)
|
96
95
|
when Nodes::Unary
|
97
96
|
firstpos(node.left)
|
98
97
|
when Nodes::Terminal
|
@@ -107,7 +106,7 @@ module ActionDispatch
|
|
107
106
|
when Nodes::Star
|
108
107
|
firstpos(node.left)
|
109
108
|
when Nodes::Or
|
110
|
-
node.children.flat_map { |c| lastpos(c) }.uniq
|
109
|
+
node.children.flat_map { |c| lastpos(c) }.tap(&:uniq!)
|
111
110
|
when Nodes::Cat
|
112
111
|
if nullable?(node.right)
|
113
112
|
lastpos(node.left) | lastpos(node.right)
|
@@ -123,17 +122,9 @@ module ActionDispatch
|
|
123
122
|
end
|
124
123
|
end
|
125
124
|
|
126
|
-
def followpos(node)
|
127
|
-
followpos_table[node]
|
128
|
-
end
|
129
|
-
|
130
125
|
private
|
131
|
-
def followpos_table
|
132
|
-
@followpos ||= build_followpos
|
133
|
-
end
|
134
|
-
|
135
126
|
def build_followpos
|
136
|
-
table = Hash.new { |h, k| h[k] = [] }
|
127
|
+
table = Hash.new { |h, k| h[k] = [] }.compare_by_identity
|
137
128
|
@ast.each do |n|
|
138
129
|
case n
|
139
130
|
when Nodes::Cat
|
@@ -150,12 +141,7 @@ module ActionDispatch
|
|
150
141
|
end
|
151
142
|
|
152
143
|
def symbol(edge)
|
153
|
-
|
154
|
-
when Journey::Nodes::Symbol
|
155
|
-
edge.regexp
|
156
|
-
else
|
157
|
-
edge.left
|
158
|
-
end
|
144
|
+
edge.symbol? ? edge.regexp : edge.left
|
159
145
|
end
|
160
146
|
end
|
161
147
|
end
|
@@ -14,6 +14,8 @@ module ActionDispatch
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class Simulator # :nodoc:
|
17
|
+
INITIAL_STATE = [0].freeze
|
18
|
+
|
17
19
|
attr_reader :tt
|
18
20
|
|
19
21
|
def initialize(transition_table)
|
@@ -22,18 +24,17 @@ module ActionDispatch
|
|
22
24
|
|
23
25
|
def memos(string)
|
24
26
|
input = StringScanner.new(string)
|
25
|
-
state =
|
27
|
+
state = INITIAL_STATE
|
28
|
+
|
26
29
|
while sym = input.scan(%r([/.?]|[^/.?]+))
|
27
30
|
state = tt.move(state, sym)
|
28
31
|
end
|
29
32
|
|
30
|
-
acceptance_states = state.
|
31
|
-
tt.accepting?
|
32
|
-
|
33
|
-
|
34
|
-
return yield if acceptance_states.empty?
|
33
|
+
acceptance_states = state.each_with_object([]) do |s, memos|
|
34
|
+
memos.concat(tt.memo(s)) if tt.accepting?(s)
|
35
|
+
end
|
35
36
|
|
36
|
-
acceptance_states.
|
37
|
+
acceptance_states.empty? ? yield : acceptance_states
|
37
38
|
end
|
38
39
|
end
|
39
40
|
end
|
@@ -45,16 +45,18 @@ module ActionDispatch
|
|
45
45
|
return [] if t.empty?
|
46
46
|
|
47
47
|
regexps = []
|
48
|
+
strings = []
|
48
49
|
|
49
|
-
t.
|
50
|
+
t.each { |s|
|
50
51
|
if states = @regexp_states[s]
|
51
|
-
|
52
|
+
states.each { |re, v| regexps << v if re.match?(a) && !v.nil? }
|
52
53
|
end
|
53
54
|
|
54
55
|
if states = @string_states[s]
|
55
|
-
states[a]
|
56
|
+
strings << states[a] unless states[a].nil?
|
56
57
|
end
|
57
|
-
}
|
58
|
+
}
|
59
|
+
strings.concat regexps
|
58
60
|
end
|
59
61
|
|
60
62
|
def as_json(options = nil)
|