actionpack 6.1.5 → 7.0.0.alpha1
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 +99 -521
- data/MIT-LICENSE +2 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +7 -21
- data/lib/abstract_controller/caching/fragments.rb +2 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +9 -8
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +3 -2
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/translation.rb +0 -2
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/action_controller/api.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +38 -1
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +5 -13
- data/lib/action_controller/metal/exceptions.rb +19 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/http_authentication.rb +15 -16
- data/lib/action_controller/metal/instrumentation.rb +55 -52
- data/lib/action_controller/metal/live.rb +42 -2
- data/lib/action_controller/metal/mime_responds.rb +3 -3
- data/lib/action_controller/metal/params_wrapper.rb +7 -7
- data/lib/action_controller/metal/permissions_policy.rb +1 -1
- data/lib/action_controller/metal/query_tags.rb +16 -0
- data/lib/action_controller/metal/redirecting.rb +50 -16
- data/lib/action_controller/metal/rendering.rb +7 -7
- data/lib/action_controller/metal/request_forgery_protection.rb +64 -20
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +1 -3
- data/lib/action_controller/metal/strong_parameters.rb +25 -29
- data/lib/action_controller/metal/testing.rb +0 -2
- data/lib/action_controller/metal.rb +7 -10
- data/lib/action_controller/railtie.rb +42 -5
- data/lib/action_controller/test_case.rb +6 -2
- data/lib/action_controller.rb +2 -5
- data/lib/action_dispatch/http/cache.rb +13 -6
- data/lib/action_dispatch/http/content_security_policy.rb +40 -37
- data/lib/action_dispatch/http/filter_parameters.rb +5 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +13 -3
- data/lib/action_dispatch/http/mime_type.rb +9 -11
- data/lib/action_dispatch/http/parameters.rb +4 -4
- data/lib/action_dispatch/http/permissions_policy.rb +1 -1
- data/lib/action_dispatch/http/request.rb +10 -19
- data/lib/action_dispatch/http/response.rb +3 -3
- data/lib/action_dispatch/http/url.rb +9 -10
- data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
- data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
- data/lib/action_dispatch/journey/nodes/node.rb +70 -5
- data/lib/action_dispatch/journey/path/pattern.rb +22 -13
- data/lib/action_dispatch/journey/route.rb +5 -12
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +1 -1
- data/lib/action_dispatch/journey/routes.rb +3 -3
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
- data/lib/action_dispatch/middleware/cookies.rb +7 -3
- data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
- data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
- data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
- data/lib/action_dispatch/middleware/executor.rb +1 -1
- data/lib/action_dispatch/middleware/flash.rb +9 -11
- data/lib/action_dispatch/middleware/host_authorization.rb +25 -73
- data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +6 -18
- data/lib/action_dispatch/middleware/stack.rb +50 -9
- data/lib/action_dispatch/middleware/static.rb +2 -5
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +43 -13
- data/lib/action_dispatch/routing/mapper.rb +44 -72
- data/lib/action_dispatch/routing/redirection.rb +0 -2
- data/lib/action_dispatch/routing/route_set.rb +7 -4
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +1 -2
- data/lib/action_dispatch/routing.rb +2 -2
- data/lib/action_dispatch/system_test_case.rb +6 -12
- data/lib/action_dispatch/system_testing/driver.rb +24 -4
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +10 -6
- data/lib/action_dispatch/testing/assertions.rb +2 -5
- data/lib/action_dispatch/testing/integration.rb +6 -8
- data/lib/action_dispatch/testing/test_process.rb +2 -2
- data/lib/action_dispatch.rb +1 -1
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +18 -18
@@ -42,11 +42,8 @@ module ActionDispatch
|
|
42
42
|
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
|
43
43
|
HTTP_X_FORWARDED_FOR HTTP_ORIGIN HTTP_VERSION
|
44
44
|
HTTP_X_CSRF_TOKEN HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST
|
45
|
-
SERVER_ADDR
|
46
45
|
].freeze
|
47
46
|
|
48
|
-
# TODO: Remove SERVER_ADDR when we remove support to Rack 2.1.
|
49
|
-
# See https://github.com/rack/rack/commit/c173b188d81ee437b588c1e046a1c9f031dea550
|
50
47
|
ENV_METHODS.each do |env|
|
51
48
|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
52
49
|
# frozen_string_literal: true
|
@@ -90,7 +87,7 @@ module ActionDispatch
|
|
90
87
|
controller_param = name.underscore
|
91
88
|
const_name = controller_param.camelize << "Controller"
|
92
89
|
begin
|
93
|
-
|
90
|
+
const_name.constantize
|
94
91
|
rescue NameError => error
|
95
92
|
if error.missing_name == const_name || const_name.start_with?("#{error.missing_name}::")
|
96
93
|
raise MissingController.new(error.message, error.name)
|
@@ -165,7 +162,7 @@ module ActionDispatch
|
|
165
162
|
set_header(routes.env_key, name.dup)
|
166
163
|
end
|
167
164
|
|
168
|
-
def request_method=(request_method)
|
165
|
+
def request_method=(request_method) # :nodoc:
|
169
166
|
if check_method(request_method)
|
170
167
|
@request_method = set_header("REQUEST_METHOD", request_method)
|
171
168
|
end
|
@@ -266,7 +263,7 @@ module ActionDispatch
|
|
266
263
|
# # get "/articles"
|
267
264
|
# request.media_type # => "application/x-www-form-urlencoded"
|
268
265
|
def media_type
|
269
|
-
content_mime_type
|
266
|
+
content_mime_type&.to_s
|
270
267
|
end
|
271
268
|
|
272
269
|
# Returns the content length of the request as an integer.
|
@@ -355,21 +352,15 @@ module ActionDispatch
|
|
355
352
|
FORM_DATA_MEDIA_TYPES.include?(media_type)
|
356
353
|
end
|
357
354
|
|
358
|
-
def body_stream
|
355
|
+
def body_stream # :nodoc:
|
359
356
|
get_header("rack.input")
|
360
357
|
end
|
361
358
|
|
362
|
-
# TODO This should be broken apart into AD::Request::Session and probably
|
363
|
-
# be included by the session middleware.
|
364
359
|
def reset_session
|
365
|
-
|
366
|
-
session.destroy
|
367
|
-
else
|
368
|
-
self.session = {}
|
369
|
-
end
|
360
|
+
session.destroy
|
370
361
|
end
|
371
362
|
|
372
|
-
def session=(session)
|
363
|
+
def session=(session) # :nodoc:
|
373
364
|
Session.set self, session
|
374
365
|
end
|
375
366
|
|
@@ -434,10 +425,6 @@ module ActionDispatch
|
|
434
425
|
def commit_flash
|
435
426
|
end
|
436
427
|
|
437
|
-
def ssl?
|
438
|
-
super || scheme == "wss"
|
439
|
-
end
|
440
|
-
|
441
428
|
def inspect # :nodoc:
|
442
429
|
"#<#{self.class.name} #{method} #{original_url.dump} for #{remote_ip}>"
|
443
430
|
end
|
@@ -447,6 +434,10 @@ module ActionDispatch
|
|
447
434
|
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
|
448
435
|
name
|
449
436
|
end
|
437
|
+
|
438
|
+
def default_session
|
439
|
+
Session.disabled(self)
|
440
|
+
end
|
450
441
|
end
|
451
442
|
end
|
452
443
|
|
@@ -88,13 +88,13 @@ module ActionDispatch # :nodoc:
|
|
88
88
|
|
89
89
|
def self.return_only_media_type_on_content_type=(*)
|
90
90
|
ActiveSupport::Deprecation.warn(
|
91
|
-
".return_only_media_type_on_content_type= is
|
91
|
+
".return_only_media_type_on_content_type= is deprecated with no replacement and will be removed in 7.0."
|
92
92
|
)
|
93
93
|
end
|
94
94
|
|
95
95
|
def self.return_only_media_type_on_content_type
|
96
96
|
ActiveSupport::Deprecation.warn(
|
97
|
-
".return_only_media_type_on_content_type is
|
97
|
+
".return_only_media_type_on_content_type is deprecated with no replacement and will be removed in 7.0."
|
98
98
|
)
|
99
99
|
end
|
100
100
|
|
@@ -336,7 +336,7 @@ module ActionDispatch # :nodoc:
|
|
336
336
|
# Avoid having to pass an open file handle as the response body.
|
337
337
|
# Rack::Sendfile will usually intercept the response and uses
|
338
338
|
# the path directly, so there is no reason to open the file.
|
339
|
-
class FileBody
|
339
|
+
class FileBody # :nodoc:
|
340
340
|
attr_reader :to_path
|
341
341
|
|
342
342
|
def initialize(path)
|
@@ -222,7 +222,7 @@ module ActionDispatch
|
|
222
222
|
if forwarded = x_forwarded_host.presence
|
223
223
|
forwarded.split(/,\s?/).last
|
224
224
|
else
|
225
|
-
get_header("HTTP_HOST") || "#{server_name
|
225
|
+
get_header("HTTP_HOST") || "#{server_name}:#{get_header('SERVER_PORT')}"
|
226
226
|
end
|
227
227
|
end
|
228
228
|
|
@@ -258,12 +258,10 @@ module ActionDispatch
|
|
258
258
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
259
259
|
# req.port # => 8080
|
260
260
|
def port
|
261
|
-
@port ||=
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
standard_port
|
266
|
-
end
|
261
|
+
@port ||= if raw_host_with_port =~ /:(\d+)$/
|
262
|
+
$1.to_i
|
263
|
+
else
|
264
|
+
standard_port
|
267
265
|
end
|
268
266
|
end
|
269
267
|
|
@@ -272,9 +270,10 @@ module ActionDispatch
|
|
272
270
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
273
271
|
# req.standard_port # => 80
|
274
272
|
def standard_port
|
275
|
-
|
276
|
-
|
277
|
-
else
|
273
|
+
if "https://" == protocol
|
274
|
+
443
|
275
|
+
else
|
276
|
+
80
|
278
277
|
end
|
279
278
|
end
|
280
279
|
|
@@ -6,13 +6,13 @@ module ActionDispatch
|
|
6
6
|
module Journey # :nodoc:
|
7
7
|
module GTG # :nodoc:
|
8
8
|
class Builder # :nodoc:
|
9
|
-
|
9
|
+
DUMMY_END_NODE = Nodes::Dummy.new
|
10
10
|
|
11
11
|
attr_reader :root, :ast, :endpoints
|
12
12
|
|
13
13
|
def initialize(root)
|
14
14
|
@root = root
|
15
|
-
@ast = Nodes::Cat.new root,
|
15
|
+
@ast = Nodes::Cat.new root, DUMMY_END_NODE
|
16
16
|
@followpos = build_followpos
|
17
17
|
end
|
18
18
|
|
@@ -28,12 +28,12 @@ module ActionDispatch
|
|
28
28
|
marked[s] = true # mark s
|
29
29
|
|
30
30
|
s.group_by { |state| symbol(state) }.each do |sym, ps|
|
31
|
-
u = ps.flat_map { |l| @followpos[l] }
|
31
|
+
u = ps.flat_map { |l| @followpos[l] }.uniq
|
32
32
|
next if u.empty?
|
33
33
|
|
34
34
|
from = state_id[s]
|
35
35
|
|
36
|
-
if u.all? { |pos| pos ==
|
36
|
+
if u.all? { |pos| pos == DUMMY_END_NODE }
|
37
37
|
to = state_id[Object.new]
|
38
38
|
dtrans[from, to] = sym
|
39
39
|
dtrans.add_accepting(to)
|
@@ -43,9 +43,9 @@ module ActionDispatch
|
|
43
43
|
to = state_id[u]
|
44
44
|
dtrans[from, to] = sym
|
45
45
|
|
46
|
-
if u.include?(
|
46
|
+
if u.include?(DUMMY_END_NODE)
|
47
47
|
ps.each do |state|
|
48
|
-
if @followpos[state].include?(
|
48
|
+
if @followpos[state].include?(DUMMY_END_NODE)
|
49
49
|
dtrans.add_memo(to, state.memo)
|
50
50
|
end
|
51
51
|
end
|
@@ -66,7 +66,10 @@ module ActionDispatch
|
|
66
66
|
when Nodes::Group
|
67
67
|
true
|
68
68
|
when Nodes::Star
|
69
|
-
|
69
|
+
# the default star regex is /(.+)/ which is NOT nullable
|
70
|
+
# but since different constraints can be provided we must
|
71
|
+
# actually check if this is the case or not.
|
72
|
+
node.regexp.match?("")
|
70
73
|
when Nodes::Or
|
71
74
|
node.children.any? { |c| nullable?(c) }
|
72
75
|
when Nodes::Cat
|
@@ -104,7 +107,7 @@ module ActionDispatch
|
|
104
107
|
def lastpos(node)
|
105
108
|
case node
|
106
109
|
when Nodes::Star
|
107
|
-
|
110
|
+
lastpos(node.left)
|
108
111
|
when Nodes::Or
|
109
112
|
node.children.flat_map { |c| lastpos(c) }.tap(&:uniq!)
|
110
113
|
when Nodes::Cat
|
@@ -131,10 +134,6 @@ module ActionDispatch
|
|
131
134
|
lastpos(n.left).each do |i|
|
132
135
|
table[i] += firstpos(n.right)
|
133
136
|
end
|
134
|
-
when Nodes::Star
|
135
|
-
lastpos(n).each do |i|
|
136
|
-
table[i] += firstpos(n)
|
137
|
-
end
|
138
137
|
end
|
139
138
|
end
|
140
139
|
table
|
@@ -14,7 +14,7 @@ module ActionDispatch
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class Simulator # :nodoc:
|
17
|
-
INITIAL_STATE = [0].freeze
|
17
|
+
INITIAL_STATE = [ [0, nil] ].freeze
|
18
18
|
|
19
19
|
attr_reader :tt
|
20
20
|
|
@@ -25,13 +25,19 @@ module ActionDispatch
|
|
25
25
|
def memos(string)
|
26
26
|
input = StringScanner.new(string)
|
27
27
|
state = INITIAL_STATE
|
28
|
+
start_index = 0
|
28
29
|
|
29
30
|
while sym = input.scan(%r([/.?]|[^/.?]+))
|
30
|
-
|
31
|
+
end_index = start_index + sym.length
|
32
|
+
|
33
|
+
state = tt.move(state, string, start_index, end_index)
|
34
|
+
|
35
|
+
start_index = end_index
|
31
36
|
end
|
32
37
|
|
33
|
-
acceptance_states = state.each_with_object([]) do |
|
34
|
-
|
38
|
+
acceptance_states = state.each_with_object([]) do |s_d, memos|
|
39
|
+
s, idx = s_d
|
40
|
+
memos.concat(tt.memo(s)) if idx.nil? && tt.accepting?(s)
|
35
41
|
end
|
36
42
|
|
37
43
|
acceptance_states.empty? ? yield : acceptance_states
|
@@ -10,11 +10,15 @@ module ActionDispatch
|
|
10
10
|
|
11
11
|
attr_reader :memos
|
12
12
|
|
13
|
+
DEFAULT_EXP = /[^.\/?]+/
|
14
|
+
DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/
|
15
|
+
|
13
16
|
def initialize
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
17
|
+
@stdparam_states = {}
|
18
|
+
@regexp_states = {}
|
19
|
+
@string_states = {}
|
20
|
+
@accepting = {}
|
21
|
+
@memos = Hash.new { |h, k| h[k] = [] }
|
18
22
|
end
|
19
23
|
|
20
24
|
def add_accepting(state)
|
@@ -41,22 +45,54 @@ module ActionDispatch
|
|
41
45
|
Array(t)
|
42
46
|
end
|
43
47
|
|
44
|
-
def move(t,
|
48
|
+
def move(t, full_string, start_index, end_index)
|
45
49
|
return [] if t.empty?
|
46
50
|
|
47
|
-
|
48
|
-
strings = []
|
51
|
+
next_states = []
|
49
52
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
+
tok = full_string.slice(start_index, end_index - start_index)
|
54
|
+
token_matches_default_component = DEFAULT_EXP_ANCHORED.match?(tok)
|
55
|
+
|
56
|
+
t.each { |s, previous_start|
|
57
|
+
if previous_start.nil?
|
58
|
+
# In the simple case of a "default" param regex do this fast-path
|
59
|
+
# and add all next states.
|
60
|
+
if token_matches_default_component && states = @stdparam_states[s]
|
61
|
+
states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
|
62
|
+
end
|
63
|
+
|
64
|
+
# When we have a literal string, we can just pull the next state
|
65
|
+
if states = @string_states[s]
|
66
|
+
next_states << [states[tok], nil].freeze unless states[tok].nil?
|
67
|
+
end
|
53
68
|
end
|
54
69
|
|
55
|
-
|
56
|
-
|
70
|
+
# For regexes that aren't the "default" style, they may potentially
|
71
|
+
# not be terminated by the first "token" [./?], so we need to continue
|
72
|
+
# to attempt to match this regexp as well as any successful paths that
|
73
|
+
# continue out of it. both paths could be valid.
|
74
|
+
if states = @regexp_states[s]
|
75
|
+
slice_start = if previous_start.nil?
|
76
|
+
start_index
|
77
|
+
else
|
78
|
+
previous_start
|
79
|
+
end
|
80
|
+
|
81
|
+
slice_length = end_index - slice_start
|
82
|
+
curr_slice = full_string.slice(slice_start, slice_length)
|
83
|
+
|
84
|
+
states.each { |re, v|
|
85
|
+
# if we match, we can try moving past this
|
86
|
+
next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
|
87
|
+
}
|
88
|
+
|
89
|
+
# and regardless, we must continue accepting tokens and retrying this regexp.
|
90
|
+
# we need to remember where we started as well so we can take bigger slices.
|
91
|
+
next_states << [s, slice_start].freeze
|
57
92
|
end
|
58
93
|
}
|
59
|
-
|
94
|
+
|
95
|
+
next_states
|
60
96
|
end
|
61
97
|
|
62
98
|
def as_json(options = nil)
|
@@ -69,9 +105,10 @@ module ActionDispatch
|
|
69
105
|
end
|
70
106
|
|
71
107
|
{
|
72
|
-
regexp_states:
|
73
|
-
string_states:
|
74
|
-
|
108
|
+
regexp_states: simple_regexp,
|
109
|
+
string_states: @string_states,
|
110
|
+
stdparam_states: @stdparam_states,
|
111
|
+
accepting: @accepting
|
75
112
|
}
|
76
113
|
end
|
77
114
|
|
@@ -93,7 +130,7 @@ module ActionDispatch
|
|
93
130
|
states = "function tt() { return #{to_json}; }"
|
94
131
|
|
95
132
|
fun_routes = paths.sample(3).map do |ast|
|
96
|
-
ast.
|
133
|
+
ast.filter_map { |n|
|
97
134
|
case n
|
98
135
|
when Nodes::Symbol
|
99
136
|
case n.left
|
@@ -106,7 +143,7 @@ module ActionDispatch
|
|
106
143
|
else
|
107
144
|
nil
|
108
145
|
end
|
109
|
-
}.
|
146
|
+
}.join
|
110
147
|
end
|
111
148
|
|
112
149
|
stylesheets = [fsm_css]
|
@@ -125,18 +162,33 @@ module ActionDispatch
|
|
125
162
|
|
126
163
|
def []=(from, to, sym)
|
127
164
|
to_mappings = states_hash_for(sym)[from] ||= {}
|
165
|
+
case sym
|
166
|
+
when Regexp
|
167
|
+
# we must match the whole string to a token boundary
|
168
|
+
if sym == DEFAULT_EXP
|
169
|
+
sym = DEFAULT_EXP_ANCHORED
|
170
|
+
else
|
171
|
+
sym = /\A#{sym}\Z/
|
172
|
+
end
|
173
|
+
when Symbol
|
174
|
+
# account for symbols in the constraints the same as strings
|
175
|
+
sym = sym.to_s
|
176
|
+
end
|
128
177
|
to_mappings[sym] = to
|
129
178
|
end
|
130
179
|
|
131
180
|
def states
|
132
181
|
ss = @string_states.keys + @string_states.values.flat_map(&:values)
|
182
|
+
ps = @stdparam_states.keys + @stdparam_states.values.flat_map(&:values)
|
133
183
|
rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
|
134
|
-
(ss + rs).uniq
|
184
|
+
(ss + ps + rs).uniq
|
135
185
|
end
|
136
186
|
|
137
187
|
def transitions
|
138
188
|
@string_states.flat_map { |from, hash|
|
139
189
|
hash.map { |s, to| [from, s, to] }
|
190
|
+
} + @stdparam_states.flat_map { |from, hash|
|
191
|
+
hash.map { |s, to| [from, s, to] }
|
140
192
|
} + @regexp_states.flat_map { |from, hash|
|
141
193
|
hash.map { |s, to| [from, s, to] }
|
142
194
|
}
|
@@ -145,10 +197,14 @@ module ActionDispatch
|
|
145
197
|
private
|
146
198
|
def states_hash_for(sym)
|
147
199
|
case sym
|
148
|
-
when String
|
200
|
+
when String, Symbol
|
149
201
|
@string_states
|
150
202
|
when Regexp
|
151
|
-
|
203
|
+
if sym == DEFAULT_EXP
|
204
|
+
@stdparam_states
|
205
|
+
else
|
206
|
+
@regexp_states
|
207
|
+
end
|
152
208
|
else
|
153
209
|
raise ArgumentError, "unknown symbol: %s" % sym.class
|
154
210
|
end
|
@@ -4,6 +4,66 @@ require "action_dispatch/journey/visitors"
|
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
module Journey # :nodoc:
|
7
|
+
class Ast # :nodoc:
|
8
|
+
attr_reader :names, :path_params, :tree, :wildcard_options, :terminals
|
9
|
+
alias :root :tree
|
10
|
+
|
11
|
+
def initialize(tree, formatted)
|
12
|
+
@tree = tree
|
13
|
+
@path_params = []
|
14
|
+
@names = []
|
15
|
+
@symbols = []
|
16
|
+
@stars = []
|
17
|
+
@terminals = []
|
18
|
+
@wildcard_options = {}
|
19
|
+
|
20
|
+
visit_tree(formatted)
|
21
|
+
end
|
22
|
+
|
23
|
+
def requirements=(requirements)
|
24
|
+
# inject any regexp requirements for `star` nodes so they can be
|
25
|
+
# determined nullable, which requires knowing if the regex accepts an
|
26
|
+
# empty string.
|
27
|
+
(symbols + stars).each do |node|
|
28
|
+
re = requirements[node.to_sym]
|
29
|
+
node.regexp = re if re
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def route=(route)
|
34
|
+
terminals.each { |n| n.memo = route }
|
35
|
+
end
|
36
|
+
|
37
|
+
def glob?
|
38
|
+
stars.any?
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
attr_reader :symbols, :stars
|
43
|
+
|
44
|
+
def visit_tree(formatted)
|
45
|
+
tree.each do |node|
|
46
|
+
if node.symbol?
|
47
|
+
path_params << node.to_sym
|
48
|
+
names << node.name
|
49
|
+
symbols << node
|
50
|
+
elsif node.star?
|
51
|
+
stars << node
|
52
|
+
|
53
|
+
if formatted != false
|
54
|
+
# Add a constraint for wildcard route to make it non-greedy and
|
55
|
+
# match the optional format part of the route by default.
|
56
|
+
wildcard_options[node.name.to_sym] ||= /.+?/
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if node.terminal?
|
61
|
+
terminals << node
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
7
67
|
module Nodes # :nodoc:
|
8
68
|
class Node # :nodoc:
|
9
69
|
include Enumerable
|
@@ -78,7 +138,7 @@ module ActionDispatch
|
|
78
138
|
alias :symbol :regexp
|
79
139
|
attr_reader :name
|
80
140
|
|
81
|
-
DEFAULT_EXP = /[
|
141
|
+
DEFAULT_EXP = /[^.\/?]+/
|
82
142
|
GREEDY_EXP = /(.+)/
|
83
143
|
def initialize(left, regexp = DEFAULT_EXP)
|
84
144
|
super(left)
|
@@ -86,10 +146,6 @@ module ActionDispatch
|
|
86
146
|
@name = -left.tr("*:", "")
|
87
147
|
end
|
88
148
|
|
89
|
-
def default_regexp?
|
90
|
-
regexp == DEFAULT_EXP
|
91
|
-
end
|
92
|
-
|
93
149
|
def type; :SYMBOL; end
|
94
150
|
def symbol?; true; end
|
95
151
|
end
|
@@ -104,6 +160,15 @@ module ActionDispatch
|
|
104
160
|
end
|
105
161
|
|
106
162
|
class Star < Unary # :nodoc:
|
163
|
+
attr_accessor :regexp
|
164
|
+
|
165
|
+
def initialize(left)
|
166
|
+
super(left)
|
167
|
+
|
168
|
+
# By default wildcard routes are non-greedy and must match something.
|
169
|
+
@regexp = /.+?/
|
170
|
+
end
|
171
|
+
|
107
172
|
def star?; true; end
|
108
173
|
def type; :STAR; end
|
109
174
|
|
@@ -4,15 +4,16 @@ module ActionDispatch
|
|
4
4
|
module Journey # :nodoc:
|
5
5
|
module Path # :nodoc:
|
6
6
|
class Pattern # :nodoc:
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :ast, :names, :requirements, :anchored, :spec
|
8
8
|
|
9
9
|
def initialize(ast, requirements, separators, anchored)
|
10
|
-
@
|
10
|
+
@ast = ast
|
11
|
+
@spec = ast.root
|
11
12
|
@requirements = requirements
|
12
13
|
@separators = separators
|
13
14
|
@anchored = anchored
|
14
15
|
|
15
|
-
@names =
|
16
|
+
@names = ast.names
|
16
17
|
@optional_names = nil
|
17
18
|
@required_names = nil
|
18
19
|
@re = nil
|
@@ -27,20 +28,28 @@ module ActionDispatch
|
|
27
28
|
required_names
|
28
29
|
offsets
|
29
30
|
to_regexp
|
30
|
-
nil
|
31
|
+
@ast = nil
|
31
32
|
end
|
32
33
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
node.regexp = re if re
|
37
|
-
end
|
34
|
+
def requirements_anchored?
|
35
|
+
# each required param must not be surrounded by a literal, otherwise it isn't simple to chunk-match the url piecemeal
|
36
|
+
terminals = ast.terminals
|
38
37
|
|
39
|
-
|
40
|
-
|
38
|
+
terminals.each_with_index { |s, index|
|
39
|
+
next if index < 1
|
40
|
+
next if s.type == :DOT || s.type == :SLASH
|
41
|
+
|
42
|
+
back = terminals[index - 1]
|
43
|
+
fwd = terminals[index + 1]
|
44
|
+
|
45
|
+
# we also don't support this yet, constraints must be regexps
|
46
|
+
return false if s.symbol? && s.regexp.is_a?(Array)
|
47
|
+
|
48
|
+
return false if back.literal?
|
49
|
+
return false if !fwd.nil? && fwd.literal?
|
50
|
+
}
|
41
51
|
|
42
|
-
|
43
|
-
@names ||= spec.find_all(&:symbol?).map(&:name)
|
52
|
+
true
|
44
53
|
end
|
45
54
|
|
46
55
|
def required_names
|
@@ -5,7 +5,7 @@ module ActionDispatch
|
|
5
5
|
module Journey
|
6
6
|
class Route
|
7
7
|
attr_reader :app, :path, :defaults, :name, :precedence, :constraints,
|
8
|
-
:internal, :scope_options
|
8
|
+
:internal, :scope_options, :ast
|
9
9
|
|
10
10
|
alias :conditions :constraints
|
11
11
|
|
@@ -65,29 +65,22 @@ module ActionDispatch
|
|
65
65
|
@_required_defaults = required_defaults
|
66
66
|
@required_parts = nil
|
67
67
|
@parts = nil
|
68
|
-
@decorated_ast = nil
|
69
68
|
@precedence = precedence
|
70
69
|
@path_formatter = @path.build_formatter
|
71
70
|
@scope_options = scope_options
|
72
71
|
@internal = internal
|
72
|
+
|
73
|
+
@ast = @path.ast.root
|
74
|
+
@path.ast.route = self
|
73
75
|
end
|
74
76
|
|
75
77
|
def eager_load!
|
76
78
|
path.eager_load!
|
77
|
-
ast
|
78
79
|
parts
|
79
80
|
required_defaults
|
80
81
|
nil
|
81
82
|
end
|
82
83
|
|
83
|
-
def ast
|
84
|
-
@decorated_ast ||= begin
|
85
|
-
decorated_ast = path.ast
|
86
|
-
decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
|
87
|
-
decorated_ast
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
84
|
# Needed for `bin/rails routes`. Picks up succinctly defined requirements
|
92
85
|
# for a route, for example route
|
93
86
|
#
|
@@ -142,7 +135,7 @@ module ActionDispatch
|
|
142
135
|
end
|
143
136
|
|
144
137
|
def glob?
|
145
|
-
path.
|
138
|
+
path.ast.glob?
|
146
139
|
end
|
147
140
|
|
148
141
|
def dispatcher?
|
@@ -35,7 +35,7 @@ module ActionDispatch
|
|
35
35
|
US_ASCII = Encoding::US_ASCII
|
36
36
|
UTF_8 = Encoding::UTF_8
|
37
37
|
EMPTY = (+"").force_encoding(US_ASCII).freeze
|
38
|
-
DEC2HEX = (0..255).
|
38
|
+
DEC2HEX = (0..255).map { |i| (ENCODE % i).force_encoding(US_ASCII) }
|
39
39
|
|
40
40
|
ALPHA = "a-zA-Z"
|
41
41
|
DIGIT = "0-9"
|
@@ -44,7 +44,7 @@ module ActionDispatch
|
|
44
44
|
|
45
45
|
ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
|
46
46
|
|
47
|
-
FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}
|
47
|
+
FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/?]/.freeze
|
48
48
|
SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
|
49
49
|
PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
|
50
50
|
|
@@ -41,7 +41,7 @@ module ActionDispatch
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def partition_route(route)
|
44
|
-
if route.path.anchored && route.
|
44
|
+
if route.path.anchored && route.path.requirements_anchored?
|
45
45
|
anchored_routes << route
|
46
46
|
else
|
47
47
|
custom_routes << route
|
@@ -50,8 +50,8 @@ module ActionDispatch
|
|
50
50
|
|
51
51
|
def ast
|
52
52
|
@ast ||= begin
|
53
|
-
|
54
|
-
Nodes::Or.new(
|
53
|
+
nodes = anchored_routes.map(&:ast)
|
54
|
+
Nodes::Or.new(nodes)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|