actionpack 8.0.4 → 8.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +241 -173
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +10 -2
- data/lib/abstract_controller/caching.rb +6 -3
- data/lib/abstract_controller/helpers.rb +1 -1
- data/lib/abstract_controller/logger.rb +2 -1
- data/lib/action_controller/base.rb +1 -1
- data/lib/action_controller/caching.rb +1 -2
- data/lib/action_controller/form_builder.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +7 -0
- data/lib/action_controller/metal/allow_browser.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +25 -0
- data/lib/action_controller/metal/data_streaming.rb +1 -3
- data/lib/action_controller/metal/exceptions.rb +5 -0
- data/lib/action_controller/metal/flash.rb +1 -4
- data/lib/action_controller/metal/head.rb +3 -1
- data/lib/action_controller/metal/permissions_policy.rb +9 -0
- data/lib/action_controller/metal/rate_limiting.rb +22 -7
- data/lib/action_controller/metal/redirecting.rb +61 -5
- data/lib/action_controller/metal/renderers.rb +27 -6
- data/lib/action_controller/metal/rendering.rb +7 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +18 -10
- data/lib/action_controller/metal/rescue.rb +9 -0
- data/lib/action_controller/railtie.rb +2 -6
- data/lib/action_dispatch/http/cache.rb +111 -1
- data/lib/action_dispatch/http/filter_parameters.rb +5 -3
- data/lib/action_dispatch/http/mime_types.rb +1 -0
- data/lib/action_dispatch/http/param_builder.rb +28 -27
- data/lib/action_dispatch/http/parameters.rb +3 -3
- data/lib/action_dispatch/http/permissions_policy.rb +4 -0
- data/lib/action_dispatch/http/query_parser.rb +12 -10
- data/lib/action_dispatch/http/request.rb +10 -5
- data/lib/action_dispatch/http/response.rb +16 -3
- data/lib/action_dispatch/http/url.rb +99 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
- data/lib/action_dispatch/journey/gtg/transition_table.rb +33 -43
- data/lib/action_dispatch/journey/nodes/node.rb +2 -1
- data/lib/action_dispatch/journey/route.rb +45 -31
- data/lib/action_dispatch/journey/router/utils.rb +8 -14
- data/lib/action_dispatch/journey/router.rb +59 -81
- data/lib/action_dispatch/journey/routes.rb +7 -0
- data/lib/action_dispatch/journey/visitors.rb +55 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/middleware/cookies.rb +4 -2
- data/lib/action_dispatch/middleware/debug_exceptions.rb +7 -1
- data/lib/action_dispatch/middleware/debug_view.rb +11 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +11 -5
- data/lib/action_dispatch/middleware/executor.rb +12 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +1 -5
- data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/railtie.rb +10 -2
- data/lib/action_dispatch/routing/inspector.rb +4 -1
- data/lib/action_dispatch/routing/mapper.rb +323 -173
- data/lib/action_dispatch/routing/route_set.rb +2 -4
- data/lib/action_dispatch/routing/routes_proxy.rb +0 -1
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
- data/lib/action_dispatch/testing/assertions/response.rb +14 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +11 -3
- data/lib/action_dispatch/testing/integration.rb +4 -3
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +11 -10
|
@@ -47,25 +47,27 @@ module ActionDispatch
|
|
|
47
47
|
Array(t)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def move(t, full_string, start_index,
|
|
50
|
+
def move(t, full_string, token, start_index, token_matches_default)
|
|
51
51
|
return [] if t.empty?
|
|
52
52
|
|
|
53
53
|
next_states = []
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
transitions_count = t.size
|
|
56
|
+
i = 0
|
|
57
|
+
while i < transitions_count
|
|
58
|
+
s = t[i]
|
|
59
|
+
previous_start = t[i + 1]
|
|
59
60
|
if previous_start.nil?
|
|
60
61
|
# In the simple case of a "default" param regex do this fast-path and add all
|
|
61
62
|
# next states.
|
|
62
|
-
if
|
|
63
|
-
|
|
63
|
+
if token_matches_default && std_state = @stdparam_states[s]
|
|
64
|
+
next_states << std_state << nil
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
# When we have a literal string, we can just pull the next state
|
|
67
68
|
if states = @string_states[s]
|
|
68
|
-
|
|
69
|
+
state = states[token]
|
|
70
|
+
next_states << state << nil unless state.nil?
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
|
|
@@ -80,19 +82,21 @@ module ActionDispatch
|
|
|
80
82
|
previous_start
|
|
81
83
|
end
|
|
82
84
|
|
|
83
|
-
slice_length =
|
|
85
|
+
slice_length = start_index + token.length - slice_start
|
|
84
86
|
curr_slice = full_string.slice(slice_start, slice_length)
|
|
85
87
|
|
|
86
88
|
states.each { |re, v|
|
|
87
89
|
# if we match, we can try moving past this
|
|
88
|
-
next_states <<
|
|
90
|
+
next_states << v << nil if !v.nil? && re.match?(curr_slice)
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
# and regardless, we must continue accepting tokens and retrying this regexp. we
|
|
92
94
|
# need to remember where we started as well so we can take bigger slices.
|
|
93
|
-
next_states <<
|
|
95
|
+
next_states << s << slice_start
|
|
94
96
|
end
|
|
95
|
-
|
|
97
|
+
|
|
98
|
+
i += 2
|
|
99
|
+
end
|
|
96
100
|
|
|
97
101
|
next_states
|
|
98
102
|
end
|
|
@@ -107,10 +111,10 @@ module ActionDispatch
|
|
|
107
111
|
end
|
|
108
112
|
|
|
109
113
|
{
|
|
110
|
-
regexp_states: simple_regexp
|
|
111
|
-
string_states: @string_states
|
|
112
|
-
stdparam_states: @stdparam_states
|
|
113
|
-
accepting: @accepting
|
|
114
|
+
regexp_states: simple_regexp,
|
|
115
|
+
string_states: @string_states,
|
|
116
|
+
stdparam_states: @stdparam_states,
|
|
117
|
+
accepting: @accepting
|
|
114
118
|
}
|
|
115
119
|
end
|
|
116
120
|
|
|
@@ -163,25 +167,27 @@ module ActionDispatch
|
|
|
163
167
|
end
|
|
164
168
|
|
|
165
169
|
def []=(from, to, sym)
|
|
166
|
-
to_mappings = states_hash_for(sym)[from] ||= {}
|
|
167
170
|
case sym
|
|
171
|
+
when String, Symbol
|
|
172
|
+
to_mapping = @string_states[from] ||= {}
|
|
173
|
+
# account for symbols in the constraints the same as strings
|
|
174
|
+
to_mapping[sym.to_s] = to
|
|
168
175
|
when Regexp
|
|
169
|
-
# we must match the whole string to a token boundary
|
|
170
176
|
if sym == DEFAULT_EXP
|
|
171
|
-
|
|
177
|
+
@stdparam_states[from] = to
|
|
172
178
|
else
|
|
173
|
-
|
|
179
|
+
to_mapping = @regexp_states[from] ||= {}
|
|
180
|
+
# we must match the whole string to a token boundary
|
|
181
|
+
to_mapping[/\A#{sym}\Z/] = to
|
|
174
182
|
end
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
sym = sym.to_s
|
|
183
|
+
else
|
|
184
|
+
raise ArgumentError, "unknown symbol: %s" % sym.class
|
|
178
185
|
end
|
|
179
|
-
to_mappings[sym] = to
|
|
180
186
|
end
|
|
181
187
|
|
|
182
188
|
def states
|
|
183
189
|
ss = @string_states.keys + @string_states.values.flat_map(&:values)
|
|
184
|
-
ps = @stdparam_states.keys + @stdparam_states.values
|
|
190
|
+
ps = @stdparam_states.keys + @stdparam_states.values
|
|
185
191
|
rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values)
|
|
186
192
|
(ss + ps + rs).uniq
|
|
187
193
|
end
|
|
@@ -189,28 +195,12 @@ module ActionDispatch
|
|
|
189
195
|
def transitions
|
|
190
196
|
@string_states.flat_map { |from, hash|
|
|
191
197
|
hash.map { |s, to| [from, s, to] }
|
|
192
|
-
} + @stdparam_states.
|
|
193
|
-
|
|
198
|
+
} + @stdparam_states.map { |from, to|
|
|
199
|
+
[from, DEFAULT_EXP_ANCHORED, to]
|
|
194
200
|
} + @regexp_states.flat_map { |from, hash|
|
|
195
201
|
hash.map { |s, to| [from, s, to] }
|
|
196
202
|
}
|
|
197
203
|
end
|
|
198
|
-
|
|
199
|
-
private
|
|
200
|
-
def states_hash_for(sym)
|
|
201
|
-
case sym
|
|
202
|
-
when String, Symbol
|
|
203
|
-
@string_states
|
|
204
|
-
when Regexp
|
|
205
|
-
if sym == DEFAULT_EXP
|
|
206
|
-
@stdparam_states
|
|
207
|
-
else
|
|
208
|
-
@regexp_states
|
|
209
|
-
end
|
|
210
|
-
else
|
|
211
|
-
raise ArgumentError, "unknown symbol: %s" % sym.class
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
204
|
end
|
|
215
205
|
end
|
|
216
206
|
end
|
|
@@ -74,6 +74,7 @@ module ActionDispatch
|
|
|
74
74
|
def initialize(left)
|
|
75
75
|
@left = left
|
|
76
76
|
@memo = nil
|
|
77
|
+
@to_s = nil
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
def each(&block)
|
|
@@ -81,7 +82,7 @@ module ActionDispatch
|
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
def to_s
|
|
84
|
-
Visitors::String::INSTANCE.accept(self, "")
|
|
85
|
+
@to_s ||= Visitors::String::INSTANCE.accept(self, "".dup).freeze
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
def to_dot
|
|
@@ -38,29 +38,50 @@ module ActionDispatch
|
|
|
38
38
|
def self.verb; ""; end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
class Or
|
|
42
|
+
attr_reader :verb
|
|
43
|
+
|
|
44
|
+
def initialize(verbs)
|
|
45
|
+
@verbs = verbs
|
|
46
|
+
@verb = @verbs.map(&:verb).join("|")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def call(req)
|
|
50
|
+
@verbs.any? { |v| v.call req }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
41
54
|
VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash|
|
|
42
55
|
klass = const_get verb
|
|
43
56
|
hash[verb] = klass
|
|
44
57
|
hash[verb.downcase] = klass
|
|
45
58
|
hash[verb.downcase.to_sym] = klass
|
|
46
59
|
end
|
|
47
|
-
end
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
VerbMatchers::VERB_TO_CLASS.fetch(verb) do
|
|
61
|
+
VERB_TO_CLASS.default_proc = proc do |_, verb|
|
|
51
62
|
VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
|
|
52
63
|
end
|
|
64
|
+
|
|
65
|
+
def self.for(verbs)
|
|
66
|
+
if verbs.any? { |v| VERB_TO_CLASS[v] == All }
|
|
67
|
+
All
|
|
68
|
+
elsif verbs.one?
|
|
69
|
+
VERB_TO_CLASS[verbs.first]
|
|
70
|
+
else
|
|
71
|
+
Or.new(verbs.map { |v| VERB_TO_CLASS[v] })
|
|
72
|
+
end
|
|
73
|
+
end
|
|
53
74
|
end
|
|
54
75
|
|
|
55
76
|
##
|
|
56
77
|
# +path+ is a path constraint.
|
|
57
78
|
# `constraints` is a hash of constraints to be applied to this route.
|
|
58
|
-
def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {},
|
|
79
|
+
def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, via: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
|
|
59
80
|
@name = name
|
|
60
81
|
@app = app
|
|
61
82
|
@path = path
|
|
62
83
|
|
|
63
|
-
@request_method_match =
|
|
84
|
+
@request_method_match = via && VerbMatchers.for(via)
|
|
64
85
|
@constraints = constraints
|
|
65
86
|
@defaults = defaults
|
|
66
87
|
@required_defaults = nil
|
|
@@ -146,21 +167,23 @@ module ActionDispatch
|
|
|
146
167
|
end
|
|
147
168
|
|
|
148
169
|
def matches?(request)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
@request_method_match.call(request) && (
|
|
171
|
+
constraints.empty? ||
|
|
172
|
+
constraints.all? { |method, value|
|
|
173
|
+
case value
|
|
174
|
+
when Regexp, String
|
|
175
|
+
value === request.send(method).to_s
|
|
176
|
+
when Array
|
|
177
|
+
value.include?(request.send(method))
|
|
178
|
+
when TrueClass
|
|
179
|
+
request.send(method).present?
|
|
180
|
+
when FalseClass
|
|
181
|
+
request.send(method).blank?
|
|
182
|
+
else
|
|
183
|
+
value === request.send(method)
|
|
184
|
+
end
|
|
185
|
+
}
|
|
186
|
+
)
|
|
164
187
|
end
|
|
165
188
|
|
|
166
189
|
def ip
|
|
@@ -168,21 +191,12 @@ module ActionDispatch
|
|
|
168
191
|
end
|
|
169
192
|
|
|
170
193
|
def requires_matching_verb?
|
|
171
|
-
|
|
194
|
+
@request_method_match != VerbMatchers::All
|
|
172
195
|
end
|
|
173
196
|
|
|
174
197
|
def verb
|
|
175
|
-
|
|
198
|
+
@request_method_match.verb
|
|
176
199
|
end
|
|
177
|
-
|
|
178
|
-
private
|
|
179
|
-
def verbs
|
|
180
|
-
@request_method_match.map(&:verb)
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def match_verb(request)
|
|
184
|
-
@request_method_match.any? { |m| m.call request }
|
|
185
|
-
end
|
|
186
200
|
end
|
|
187
201
|
end
|
|
188
202
|
# :startdoc:
|
|
@@ -17,7 +17,14 @@ module ActionDispatch
|
|
|
17
17
|
# normalize_path("") # => "/"
|
|
18
18
|
# normalize_path("/%ab") # => "/%AB"
|
|
19
19
|
def self.normalize_path(path)
|
|
20
|
-
|
|
20
|
+
return "/".dup unless path
|
|
21
|
+
|
|
22
|
+
# Fast path for the overwhelming majority of paths that don't need to be normalized
|
|
23
|
+
if path == "/" || (path.start_with?("/") && !path.end_with?("/") && !path.match?(%r{%|//}))
|
|
24
|
+
return path.dup
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Slow path
|
|
21
28
|
encoding = path.encoding
|
|
22
29
|
path = +"/#{path}"
|
|
23
30
|
path.squeeze!("/")
|
|
@@ -61,11 +68,6 @@ module ActionDispatch
|
|
|
61
68
|
escape(segment, SEGMENT)
|
|
62
69
|
end
|
|
63
70
|
|
|
64
|
-
def unescape_uri(uri)
|
|
65
|
-
encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
|
|
66
|
-
uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
71
|
private
|
|
70
72
|
def escape(component, pattern)
|
|
71
73
|
component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
|
|
@@ -91,14 +93,6 @@ module ActionDispatch
|
|
|
91
93
|
def self.escape_fragment(fragment)
|
|
92
94
|
ENCODER.escape_fragment(fragment.to_s)
|
|
93
95
|
end
|
|
94
|
-
|
|
95
|
-
# Replaces any escaped sequences with their unescaped representations.
|
|
96
|
-
#
|
|
97
|
-
# uri = "/topics?title=Ruby%20on%20Rails"
|
|
98
|
-
# unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
|
|
99
|
-
def self.unescape_uri(uri)
|
|
100
|
-
ENCODER.unescape_uri(uri)
|
|
101
|
-
end
|
|
102
96
|
end
|
|
103
97
|
end
|
|
104
98
|
end
|
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# :markup: markdown
|
|
4
4
|
|
|
5
|
+
require "cgi/escape"
|
|
6
|
+
require "cgi/util" if RUBY_VERSION < "3.5"
|
|
5
7
|
require "action_dispatch/journey/router/utils"
|
|
6
8
|
require "action_dispatch/journey/routes"
|
|
7
9
|
require "action_dispatch/journey/formatter"
|
|
8
|
-
|
|
9
|
-
before = $-w
|
|
10
|
-
$-w = false
|
|
11
10
|
require "action_dispatch/journey/parser"
|
|
12
|
-
$-w = before
|
|
13
|
-
|
|
14
11
|
require "action_dispatch/journey/route"
|
|
15
12
|
require "action_dispatch/journey/path/pattern"
|
|
16
13
|
|
|
@@ -31,71 +28,78 @@ module ActionDispatch
|
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
def serve(req)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
script_name = req.script_name
|
|
38
|
-
|
|
39
|
-
unless route.path.anchored
|
|
40
|
-
req.script_name = (script_name.to_s + match.to_s).chomp("/")
|
|
41
|
-
req.path_info = match.post_match
|
|
42
|
-
req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
tmp_params = set_params.merge route.defaults
|
|
46
|
-
parameters.each_pair { |key, val|
|
|
47
|
-
tmp_params[key] = val.force_encoding(::Encoding::UTF_8)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
req.path_parameters = tmp_params
|
|
51
|
-
req.route_uri_pattern = route.path.spec.to_s
|
|
31
|
+
recognize(req) do |route, parameters|
|
|
32
|
+
req.path_parameters = parameters
|
|
33
|
+
req.route = route
|
|
52
34
|
|
|
53
35
|
_, headers, _ = response = route.app.serve(req)
|
|
54
36
|
|
|
55
|
-
|
|
56
|
-
req.script_name = script_name
|
|
57
|
-
req.path_info = path_info
|
|
58
|
-
req.path_parameters = set_params
|
|
59
|
-
next
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
return response
|
|
37
|
+
return response unless headers[Constants::X_CASCADE] == "pass"
|
|
63
38
|
end
|
|
64
39
|
|
|
65
40
|
[404, { Constants::X_CASCADE => "pass" }, ["Not Found"]]
|
|
66
41
|
end
|
|
67
42
|
|
|
68
|
-
def recognize(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
rails_req.path_info = match.post_match
|
|
73
|
-
rails_req.path_info = "/" + rails_req.path_info unless rails_req.path_info.start_with? "/"
|
|
74
|
-
end
|
|
43
|
+
def recognize(req, &block)
|
|
44
|
+
req_params = req.path_parameters
|
|
45
|
+
path_info = req.path_info
|
|
46
|
+
script_name = req.script_name
|
|
75
47
|
|
|
76
|
-
|
|
77
|
-
yield(route, parameters)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
48
|
+
routes = filter_routes(path_info)
|
|
80
49
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
asts = groups.values.map(&:first)
|
|
85
|
-
tt.visualizer(asts)
|
|
86
|
-
end
|
|
50
|
+
custom_routes.each { |r|
|
|
51
|
+
routes << r if r.path.match?(path_info)
|
|
52
|
+
}
|
|
87
53
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
54
|
+
if req.head?
|
|
55
|
+
routes = match_head_routes(routes, req)
|
|
56
|
+
else
|
|
57
|
+
routes.select! { |r| r.matches?(req) }
|
|
93
58
|
end
|
|
94
59
|
|
|
95
|
-
|
|
96
|
-
routes.
|
|
60
|
+
if routes.size > 1
|
|
61
|
+
routes.sort! do |a, b|
|
|
62
|
+
a.precedence <=> b.precedence
|
|
63
|
+
end
|
|
97
64
|
end
|
|
98
65
|
|
|
66
|
+
routes.each do |r|
|
|
67
|
+
match_data = r.path.match(path_info)
|
|
68
|
+
|
|
69
|
+
path_parameters = req_params.merge r.defaults
|
|
70
|
+
|
|
71
|
+
index = 1
|
|
72
|
+
match_data.names.each do |name|
|
|
73
|
+
if val = match_data[index]
|
|
74
|
+
val = if val.include?("%")
|
|
75
|
+
CGI.unescapeURIComponent(val)
|
|
76
|
+
else
|
|
77
|
+
val
|
|
78
|
+
end
|
|
79
|
+
val.force_encoding(::Encoding::UTF_8)
|
|
80
|
+
path_parameters[name.to_sym] = val
|
|
81
|
+
end
|
|
82
|
+
index += 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if r.path.anchored
|
|
86
|
+
yield(r, path_parameters)
|
|
87
|
+
else
|
|
88
|
+
req.script_name = (script_name.to_s + match_data.to_s).chomp("/")
|
|
89
|
+
req.path_info = match_data.post_match
|
|
90
|
+
req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
|
|
91
|
+
|
|
92
|
+
yield(r, path_parameters)
|
|
93
|
+
|
|
94
|
+
req.script_name = script_name
|
|
95
|
+
req.path_info = path_info
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
req.path_parameters = req_params
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
99
103
|
def simulator
|
|
100
104
|
routes.simulator
|
|
101
105
|
end
|
|
@@ -105,35 +109,9 @@ module ActionDispatch
|
|
|
105
109
|
end
|
|
106
110
|
|
|
107
111
|
def filter_routes(path)
|
|
108
|
-
return [] unless ast
|
|
109
112
|
simulator.memos(path) { [] }
|
|
110
113
|
end
|
|
111
114
|
|
|
112
|
-
def find_routes(req)
|
|
113
|
-
path_info = req.path_info
|
|
114
|
-
routes = filter_routes(path_info).concat custom_routes.find_all { |r|
|
|
115
|
-
r.path.match?(path_info)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if req.head?
|
|
119
|
-
routes = match_head_routes(routes, req)
|
|
120
|
-
else
|
|
121
|
-
routes.select! { |r| r.matches?(req) }
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
routes.sort_by!(&:precedence)
|
|
125
|
-
|
|
126
|
-
routes.each { |r|
|
|
127
|
-
match_data = r.path.match(path_info)
|
|
128
|
-
path_parameters = {}
|
|
129
|
-
match_data.names.each_with_index { |name, i|
|
|
130
|
-
val = match_data[i + 1]
|
|
131
|
-
path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
|
|
132
|
-
}
|
|
133
|
-
yield [match_data, path_parameters, r]
|
|
134
|
-
}
|
|
135
|
-
end
|
|
136
|
-
|
|
137
115
|
def match_head_routes(routes, req)
|
|
138
116
|
head_routes = routes.select { |r| r.requires_matching_verb? && r.matches?(req) }
|
|
139
117
|
return head_routes unless head_routes.empty?
|
|
@@ -72,6 +72,13 @@ module ActionDispatch
|
|
|
72
72
|
route
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
def visualizer
|
|
76
|
+
tt = GTG::Builder.new(ast).transition_table
|
|
77
|
+
groups = anchored_routes.map(&:ast).group_by(&:to_s)
|
|
78
|
+
asts = groups.values.map(&:first)
|
|
79
|
+
tt.visualizer(asts)
|
|
80
|
+
end
|
|
81
|
+
|
|
75
82
|
private
|
|
76
83
|
def clear_cache!
|
|
77
84
|
@ast = nil
|
|
@@ -128,8 +128,8 @@ module ActionDispatch
|
|
|
128
128
|
def visit_DOT(n, seed); terminal(n, seed); end
|
|
129
129
|
|
|
130
130
|
instance_methods(false).each do |pim|
|
|
131
|
-
next unless pim
|
|
132
|
-
DISPATCH_CACHE[
|
|
131
|
+
next unless pim.start_with?("visit_")
|
|
132
|
+
DISPATCH_CACHE[pim.name.delete_prefix("visit_").to_sym] = pim
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
135
|
|
|
@@ -167,32 +167,64 @@ module ActionDispatch
|
|
|
167
167
|
INSTANCE = new
|
|
168
168
|
end
|
|
169
169
|
|
|
170
|
-
class String
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
class String # :nodoc:
|
|
171
|
+
def accept(node, seed)
|
|
172
|
+
case node.type
|
|
173
|
+
when :DOT
|
|
174
|
+
seed << node.left
|
|
175
|
+
when :LITERAL
|
|
176
|
+
seed << node.left
|
|
177
|
+
when :SYMBOL
|
|
178
|
+
seed << node.left
|
|
179
|
+
when :SLASH
|
|
180
|
+
seed << node.left
|
|
181
|
+
when :CAT
|
|
182
|
+
accept(node.right, accept(node.left, seed))
|
|
183
|
+
when :STAR
|
|
184
|
+
accept(node.left, seed)
|
|
185
|
+
when :OR
|
|
177
186
|
last_child = node.children.last
|
|
178
|
-
node.children.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def visit_GROUP(node, seed)
|
|
190
|
-
visit(node.left, seed.dup << "(") << ")"
|
|
187
|
+
node.children.each do |c|
|
|
188
|
+
accept(c, seed)
|
|
189
|
+
seed << "|" unless last_child == c
|
|
190
|
+
end
|
|
191
|
+
seed
|
|
192
|
+
when :GROUP
|
|
193
|
+
accept(node.left, seed << "(") << ")"
|
|
194
|
+
else
|
|
195
|
+
raise "Unknown node type: #{node.type}"
|
|
191
196
|
end
|
|
197
|
+
end
|
|
192
198
|
|
|
193
|
-
|
|
199
|
+
INSTANCE = new
|
|
194
200
|
end
|
|
195
201
|
|
|
202
|
+
# class String < FunctionalVisitor # :nodoc:
|
|
203
|
+
# private
|
|
204
|
+
# def binary(node, seed)
|
|
205
|
+
# visit(node.right, visit(node.left, seed))
|
|
206
|
+
# end
|
|
207
|
+
#
|
|
208
|
+
# def nary(node, seed)
|
|
209
|
+
# last_child = node.children.last
|
|
210
|
+
# node.children.inject(seed) { |s, c|
|
|
211
|
+
# string = visit(c, s)
|
|
212
|
+
# string << "|" unless last_child == c
|
|
213
|
+
# string
|
|
214
|
+
# }
|
|
215
|
+
# end
|
|
216
|
+
#
|
|
217
|
+
# def terminal(node, seed)
|
|
218
|
+
# seed + node.left
|
|
219
|
+
# end
|
|
220
|
+
#
|
|
221
|
+
# def visit_GROUP(node, seed)
|
|
222
|
+
# visit(node.left, seed.dup << "(") << ")"
|
|
223
|
+
# end
|
|
224
|
+
#
|
|
225
|
+
# INSTANCE = new
|
|
226
|
+
# end
|
|
227
|
+
|
|
196
228
|
class Dot < FunctionalVisitor # :nodoc:
|
|
197
229
|
def initialize
|
|
198
230
|
@nodes = []
|
|
@@ -105,12 +105,10 @@ function match(input) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
if(stdparam_states[state] && default_re.test(token)) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
new_states.push([new_state, null]);
|
|
113
|
-
}
|
|
108
|
+
var new_state = stdparam_states[state];
|
|
109
|
+
highlight_edge(state, new_state);
|
|
110
|
+
highlight_state(new_state);
|
|
111
|
+
new_states.push([new_state, null]);
|
|
114
112
|
}
|
|
115
113
|
}
|
|
116
114
|
|
|
@@ -610,8 +610,10 @@ module ActionDispatch
|
|
|
610
610
|
end
|
|
611
611
|
|
|
612
612
|
def check_for_overflow!(name, options)
|
|
613
|
-
|
|
614
|
-
|
|
613
|
+
total_size = name.to_s.bytesize + options[:value].bytesize
|
|
614
|
+
|
|
615
|
+
if total_size > MAX_COOKIE_SIZE
|
|
616
|
+
raise CookieOverflow, "#{name} cookie overflowed with size #{total_size} bytes"
|
|
615
617
|
end
|
|
616
618
|
end
|
|
617
619
|
end
|
|
@@ -127,6 +127,7 @@ module ActionDispatch
|
|
|
127
127
|
trace_to_show: wrapper.trace_to_show,
|
|
128
128
|
routes_inspector: routes_inspector(wrapper),
|
|
129
129
|
source_extracts: wrapper.source_extracts,
|
|
130
|
+
exception_message_for_copy: compose_exception_message(wrapper).join("\n"),
|
|
130
131
|
)
|
|
131
132
|
end
|
|
132
133
|
|
|
@@ -140,6 +141,11 @@ module ActionDispatch
|
|
|
140
141
|
return unless logger
|
|
141
142
|
return if !log_rescued_responses?(request) && wrapper.rescue_response?
|
|
142
143
|
|
|
144
|
+
message = compose_exception_message(wrapper)
|
|
145
|
+
log_array(logger, message, request)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def compose_exception_message(wrapper)
|
|
143
149
|
trace = wrapper.exception_trace
|
|
144
150
|
|
|
145
151
|
message = []
|
|
@@ -168,7 +174,7 @@ module ActionDispatch
|
|
|
168
174
|
end
|
|
169
175
|
end
|
|
170
176
|
|
|
171
|
-
|
|
177
|
+
message
|
|
172
178
|
end
|
|
173
179
|
|
|
174
180
|
def log_array(logger, lines, request)
|