actionpack 6.1.7 → 7.0.4.1
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 +269 -406
- data/MIT-LICENSE +1 -0
- data/README.rdoc +2 -3
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +13 -26
- data/lib/abstract_controller/caching/fragments.rb +2 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +21 -7
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +4 -3
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/translation.rb +3 -2
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/action_controller/api.rb +6 -6
- data/lib/action_controller/base.rb +5 -4
- data/lib/action_controller/form_builder.rb +2 -2
- data/lib/action_controller/log_subscriber.rb +4 -3
- data/lib/action_controller/metal/conditional_get.rb +39 -2
- data/lib/action_controller/metal/content_security_policy.rb +36 -2
- 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/helpers.rb +2 -2
- data/lib/action_controller/metal/http_authentication.rb +66 -39
- data/lib/action_controller/metal/instrumentation.rb +57 -52
- data/lib/action_controller/metal/live.rb +43 -2
- data/lib/action_controller/metal/mime_responds.rb +3 -3
- data/lib/action_controller/metal/params_wrapper.rb +20 -11
- data/lib/action_controller/metal/permissions_policy.rb +19 -28
- data/lib/action_controller/metal/redirecting.rb +93 -18
- data/lib/action_controller/metal/renderers.rb +10 -11
- data/lib/action_controller/metal/rendering.rb +8 -8
- data/lib/action_controller/metal/request_forgery_protection.rb +78 -29
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +6 -8
- data/lib/action_controller/metal/strong_parameters.rb +100 -54
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +3 -3
- data/lib/action_controller/metal.rb +10 -13
- data/lib/action_controller/railtie.rb +49 -6
- data/lib/action_controller/renderer.rb +1 -1
- data/lib/action_controller/test_case.rb +28 -7
- data/lib/action_controller.rb +2 -5
- data/lib/action_dispatch/http/cache.rb +14 -7
- data/lib/action_dispatch/http/content_security_policy.rb +108 -35
- data/lib/action_dispatch/http/filter_parameters.rb +5 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
- data/lib/action_dispatch/http/mime_type.rb +9 -11
- data/lib/action_dispatch/http/parameters.rb +5 -5
- data/lib/action_dispatch/http/permissions_policy.rb +17 -1
- data/lib/action_dispatch/http/request.rb +12 -21
- data/lib/action_dispatch/http/response.rb +3 -16
- data/lib/action_dispatch/http/url.rb +11 -19
- 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 +6 -13
- 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 +42 -27
- 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 +3 -0
- data/lib/action_dispatch/middleware/flash.rb +17 -18
- data/lib/action_dispatch/middleware/host_authorization.rb +1 -12
- data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/server_timing.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
- data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
- data/lib/action_dispatch/middleware/show_exceptions.rb +7 -9
- data/lib/action_dispatch/middleware/stack.rb +27 -9
- data/lib/action_dispatch/middleware/static.rb +2 -6
- 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 +3 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +2 -0
- 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/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +59 -83
- data/lib/action_dispatch/routing/redirection.rb +5 -2
- data/lib/action_dispatch/routing/route_set.rb +17 -7
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +4 -5
- data/lib/action_dispatch/routing.rb +5 -6
- data/lib/action_dispatch/system_test_case.rb +5 -5
- data/lib/action_dispatch/system_testing/browser.rb +2 -12
- data/lib/action_dispatch/system_testing/driver.rb +35 -11
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +11 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
- 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 +3 -29
- data/lib/action_dispatch/testing/test_response.rb +20 -2
- data/lib/action_dispatch.rb +1 -0
- data/lib/action_pack/gem_version.rb +5 -5
- data/lib/action_pack/version.rb +1 -1
- metadata +16 -15
@@ -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] ||= /.+?/m
|
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 = /.+?/m
|
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
|
#
|
@@ -98,7 +91,7 @@ module ActionDispatch
|
|
98
91
|
# as requirements.
|
99
92
|
def requirements
|
100
93
|
@defaults.merge(path.requirements).delete_if { |_, v|
|
101
|
-
/.+?/ == v
|
94
|
+
/.+?/m == v
|
102
95
|
}
|
103
96
|
end
|
104
97
|
|
@@ -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
|
|
@@ -68,7 +68,7 @@ function highlight_state(index, color) {
|
|
68
68
|
}
|
69
69
|
|
70
70
|
function highlight_finish(index) {
|
71
|
-
svg_nodes[index].select('
|
71
|
+
svg_nodes[index].select('ellipse')
|
72
72
|
.style("fill", "while")
|
73
73
|
.transition().duration(500)
|
74
74
|
.style("fill", "blue");
|
@@ -76,54 +76,79 @@ function highlight_finish(index) {
|
|
76
76
|
|
77
77
|
function match(input) {
|
78
78
|
reset_graph();
|
79
|
-
var table
|
80
|
-
var states
|
81
|
-
var regexp_states
|
82
|
-
var string_states
|
83
|
-
var
|
79
|
+
var table = tt();
|
80
|
+
var states = [[0, null]];
|
81
|
+
var regexp_states = table['regexp_states'];
|
82
|
+
var string_states = table['string_states'];
|
83
|
+
var stdparam_states = table['stdparam_states'];
|
84
|
+
var accepting = table['accepting'];
|
85
|
+
var default_re = new RegExp("^[^.\/?]+$");
|
86
|
+
var start_index = 0;
|
84
87
|
|
85
88
|
highlight_state(0);
|
86
89
|
|
87
90
|
tokenize(input, function(token) {
|
91
|
+
var end_index = start_index + token.length;
|
92
|
+
|
88
93
|
var new_states = [];
|
89
94
|
for(var key in states) {
|
90
|
-
var
|
95
|
+
var state_parts = states[key];
|
96
|
+
var state = state_parts[0];
|
97
|
+
var previous_start = state_parts[1];
|
98
|
+
|
99
|
+
if(previous_start == null) {
|
100
|
+
if(string_states[state] && string_states[state][token]) {
|
101
|
+
var new_state = string_states[state][token];
|
102
|
+
highlight_edge(state, new_state);
|
103
|
+
highlight_state(new_state);
|
104
|
+
new_states.push([new_state, null]);
|
105
|
+
}
|
91
106
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
107
|
+
if(stdparam_states[state] && default_re.test(token)) {
|
108
|
+
for(var key in stdparam_states[state]) {
|
109
|
+
var new_state = stdparam_states[state][key];
|
110
|
+
highlight_edge(state, new_state);
|
111
|
+
highlight_state(new_state);
|
112
|
+
new_states.push([new_state, null]);
|
113
|
+
}
|
114
|
+
}
|
97
115
|
}
|
98
116
|
|
99
117
|
if(regexp_states[state]) {
|
118
|
+
var slice_start = previous_start != null ? previous_start : start_index;
|
119
|
+
|
100
120
|
for(var key in regexp_states[state]) {
|
101
121
|
var re = new RegExp("^" + key + "$");
|
102
|
-
|
122
|
+
|
123
|
+
var accumulation = input.slice(slice_start, end_index);
|
124
|
+
|
125
|
+
if(re.test(accumulation)) {
|
103
126
|
var new_state = regexp_states[state][key];
|
104
127
|
highlight_edge(state, new_state);
|
105
128
|
highlight_state(new_state);
|
106
|
-
new_states.push(new_state);
|
129
|
+
new_states.push([new_state, null]);
|
107
130
|
}
|
131
|
+
|
132
|
+
// retry the same regexp with the accumulated data either way
|
133
|
+
new_states.push([state, slice_start]);
|
108
134
|
}
|
109
135
|
}
|
110
136
|
}
|
111
137
|
|
112
|
-
if(new_states.length == 0) {
|
113
|
-
return;
|
114
|
-
}
|
115
138
|
states = new_states;
|
139
|
+
start_index = end_index;
|
116
140
|
});
|
117
141
|
|
118
142
|
for(var key in states) {
|
119
|
-
var
|
143
|
+
var state_parts = states[key];
|
144
|
+
var state = state_parts[0];
|
145
|
+
var slice_start = state_parts[1];
|
146
|
+
|
147
|
+
// we must ignore ones that are still accepting more data
|
148
|
+
if (slice_start != null) continue;
|
149
|
+
|
120
150
|
if(accepting[state]) {
|
121
|
-
|
122
|
-
if(!regexp_states[mkey] && !string_states[mkey]) {
|
123
|
-
highlight_edge(state, mkey);
|
124
|
-
highlight_finish(mkey);
|
125
|
-
}
|
126
|
-
}
|
151
|
+
highlight_finish(state);
|
127
152
|
} else {
|
128
153
|
highlight_state(state, "red");
|
129
154
|
}
|
@@ -8,7 +8,7 @@
|
|
8
8
|
<%= style %>
|
9
9
|
<% end %>
|
10
10
|
</style>
|
11
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"
|
11
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
|
12
12
|
</head>
|
13
13
|
<body>
|
14
14
|
<div id="wrapper">
|