actionpack 7.1.5.1 → 8.1.2
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 +308 -523
- data/README.rdoc +1 -1
- data/lib/abstract_controller/asset_paths.rb +6 -2
- data/lib/abstract_controller/base.rb +104 -105
- data/lib/abstract_controller/caching/fragments.rb +50 -53
- data/lib/abstract_controller/caching.rb +8 -3
- data/lib/abstract_controller/callbacks.rb +70 -62
- data/lib/abstract_controller/collector.rb +7 -7
- data/lib/abstract_controller/deprecator.rb +2 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +71 -84
- data/lib/abstract_controller/logger.rb +4 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +13 -13
- data/lib/abstract_controller/translation.rb +12 -13
- data/lib/abstract_controller/url_for.rb +8 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/api.rb +76 -72
- data/lib/action_controller/base.rb +199 -126
- data/lib/action_controller/caching.rb +16 -14
- data/lib/action_controller/deprecator.rb +2 -0
- data/lib/action_controller/form_builder.rb +21 -18
- data/lib/action_controller/log_subscriber.rb +23 -2
- data/lib/action_controller/metal/allow_browser.rb +133 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +217 -175
- data/lib/action_controller/metal/content_security_policy.rb +25 -24
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +72 -63
- data/lib/action_controller/metal/default_headers.rb +5 -3
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
- data/lib/action_controller/metal/exceptions.rb +16 -9
- data/lib/action_controller/metal/flash.rb +13 -14
- data/lib/action_controller/metal/head.rb +15 -11
- data/lib/action_controller/metal/helpers.rb +63 -55
- data/lib/action_controller/metal/http_authentication.rb +209 -201
- data/lib/action_controller/metal/implicit_render.rb +17 -15
- data/lib/action_controller/metal/instrumentation.rb +16 -14
- data/lib/action_controller/metal/live.rb +177 -128
- data/lib/action_controller/metal/logging.rb +6 -4
- data/lib/action_controller/metal/mime_responds.rb +151 -142
- data/lib/action_controller/metal/parameter_encoding.rb +34 -32
- data/lib/action_controller/metal/params_wrapper.rb +57 -59
- data/lib/action_controller/metal/permissions_policy.rb +22 -12
- data/lib/action_controller/metal/rate_limiting.rb +92 -0
- data/lib/action_controller/metal/redirecting.rb +213 -94
- data/lib/action_controller/metal/renderers.rb +78 -57
- data/lib/action_controller/metal/rendering.rb +111 -77
- data/lib/action_controller/metal/request_forgery_protection.rb +182 -143
- data/lib/action_controller/metal/rescue.rb +20 -9
- data/lib/action_controller/metal/streaming.rb +118 -195
- data/lib/action_controller/metal/strong_parameters.rb +720 -530
- data/lib/action_controller/metal/testing.rb +2 -0
- data/lib/action_controller/metal/url_for.rb +17 -15
- data/lib/action_controller/metal.rb +86 -60
- data/lib/action_controller/railtie.rb +36 -15
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +41 -36
- data/lib/action_controller/structured_event_subscriber.rb +116 -0
- data/lib/action_controller/template_assertions.rb +4 -2
- data/lib/action_controller/test_case.rb +160 -131
- data/lib/action_controller.rb +5 -1
- data/lib/action_dispatch/constants.rb +8 -0
- data/lib/action_dispatch/deprecator.rb +2 -0
- data/lib/action_dispatch/http/cache.rb +163 -35
- data/lib/action_dispatch/http/content_disposition.rb +2 -0
- data/lib/action_dispatch/http/content_security_policy.rb +54 -39
- data/lib/action_dispatch/http/filter_parameters.rb +14 -8
- data/lib/action_dispatch/http/filter_redirect.rb +22 -1
- data/lib/action_dispatch/http/headers.rb +22 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +89 -41
- data/lib/action_dispatch/http/mime_type.rb +25 -21
- data/lib/action_dispatch/http/mime_types.rb +3 -0
- data/lib/action_dispatch/http/param_builder.rb +187 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/parameters.rb +14 -12
- data/lib/action_dispatch/http/permissions_policy.rb +25 -36
- data/lib/action_dispatch/http/query_parser.rb +55 -0
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +141 -92
- data/lib/action_dispatch/http/response.rb +137 -77
- data/lib/action_dispatch/http/upload.rb +18 -16
- data/lib/action_dispatch/http/url.rb +187 -89
- data/lib/action_dispatch/journey/formatter.rb +21 -9
- data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +34 -11
- data/lib/action_dispatch/journey/gtg/transition_table.rb +47 -53
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +8 -6
- data/lib/action_dispatch/journey/parser.rb +99 -195
- data/lib/action_dispatch/journey/path/pattern.rb +4 -1
- data/lib/action_dispatch/journey/route.rb +54 -38
- data/lib/action_dispatch/journey/router/utils.rb +22 -27
- data/lib/action_dispatch/journey/router.rb +63 -83
- data/lib/action_dispatch/journey/routes.rb +11 -2
- data/lib/action_dispatch/journey/scanner.rb +46 -42
- data/lib/action_dispatch/journey/visitors.rb +57 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/log_subscriber.rb +7 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
- data/lib/action_dispatch/middleware/callbacks.rb +3 -1
- data/lib/action_dispatch/middleware/cookies.rb +125 -106
- data/lib/action_dispatch/middleware/debug_exceptions.rb +37 -8
- data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
- data/lib/action_dispatch/middleware/debug_view.rb +13 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +18 -23
- data/lib/action_dispatch/middleware/executor.rb +19 -4
- data/lib/action_dispatch/middleware/flash.rb +63 -51
- data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
- data/lib/action_dispatch/middleware/public_exceptions.rb +14 -12
- data/lib/action_dispatch/middleware/reloader.rb +5 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +87 -77
- data/lib/action_dispatch/middleware/request_id.rb +16 -10
- data/lib/action_dispatch/middleware/server_timing.rb +4 -2
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +30 -8
- data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
- data/lib/action_dispatch/middleware/ssl.rb +53 -40
- data/lib/action_dispatch/middleware/stack.rb +11 -10
- data/lib/action_dispatch/middleware/static.rb +33 -31
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
- 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 +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -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/middleware/templates/routes/_table.html.erb +1 -1
- data/lib/action_dispatch/railtie.rb +23 -3
- data/lib/action_dispatch/request/session.rb +24 -21
- data/lib/action_dispatch/request/utils.rb +11 -3
- data/lib/action_dispatch/routing/endpoint.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +85 -60
- data/lib/action_dispatch/routing/mapper.rb +1031 -851
- data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
- data/lib/action_dispatch/routing/redirection.rb +47 -39
- data/lib/action_dispatch/routing/route_set.rb +79 -56
- data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
- data/lib/action_dispatch/routing/url_for.rb +130 -125
- data/lib/action_dispatch/routing.rb +150 -148
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_test_case.rb +91 -81
- data/lib/action_dispatch/system_testing/browser.rb +16 -23
- data/lib/action_dispatch/system_testing/driver.rb +2 -0
- data/lib/action_dispatch/system_testing/server.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +34 -23
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/testing/assertion_response.rb +9 -7
- data/lib/action_dispatch/testing/assertions/response.rb +52 -25
- data/lib/action_dispatch/testing/assertions/routing.rb +168 -87
- data/lib/action_dispatch/testing/assertions.rb +2 -0
- data/lib/action_dispatch/testing/integration.rb +233 -223
- data/lib/action_dispatch/testing/request_encoder.rb +11 -9
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +11 -8
- data/lib/action_dispatch/testing/test_request.rb +3 -1
- data/lib/action_dispatch/testing/test_response.rb +27 -26
- data/lib/action_dispatch.rb +36 -32
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +17 -16
- metadata +36 -32
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -31
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionDispatch
|
|
4
6
|
# :stopdoc:
|
|
5
7
|
module Journey
|
|
@@ -36,29 +38,50 @@ module ActionDispatch
|
|
|
36
38
|
def self.verb; ""; end
|
|
37
39
|
end
|
|
38
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
|
+
|
|
39
54
|
VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash|
|
|
40
55
|
klass = const_get verb
|
|
41
56
|
hash[verb] = klass
|
|
42
57
|
hash[verb.downcase] = klass
|
|
43
58
|
hash[verb.downcase.to_sym] = klass
|
|
44
59
|
end
|
|
45
|
-
end
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
VerbMatchers::VERB_TO_CLASS.fetch(verb) do
|
|
61
|
+
VERB_TO_CLASS.default_proc = proc do |_, verb|
|
|
49
62
|
VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
|
|
50
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
|
|
51
74
|
end
|
|
52
75
|
|
|
53
76
|
##
|
|
54
77
|
# +path+ is a path constraint.
|
|
55
|
-
#
|
|
56
|
-
def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {},
|
|
78
|
+
# `constraints` is a hash of constraints to be applied to this route.
|
|
79
|
+
def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, via: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
|
|
57
80
|
@name = name
|
|
58
81
|
@app = app
|
|
59
82
|
@path = path
|
|
60
83
|
|
|
61
|
-
@request_method_match =
|
|
84
|
+
@request_method_match = via && VerbMatchers.for(via)
|
|
62
85
|
@constraints = constraints
|
|
63
86
|
@defaults = defaults
|
|
64
87
|
@required_defaults = nil
|
|
@@ -82,14 +105,14 @@ module ActionDispatch
|
|
|
82
105
|
nil
|
|
83
106
|
end
|
|
84
107
|
|
|
85
|
-
# Needed for `bin/rails routes`. Picks up succinctly defined requirements
|
|
86
|
-
#
|
|
108
|
+
# Needed for `bin/rails routes`. Picks up succinctly defined requirements for a
|
|
109
|
+
# route, for example route
|
|
87
110
|
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
111
|
+
# get 'photo/:id', :controller => 'photos', :action => 'show',
|
|
112
|
+
# :id => /[A-Z]\d{5}/
|
|
90
113
|
#
|
|
91
|
-
# will have {:controller=>"photos", :action=>"show", :id=>/
|
|
92
|
-
#
|
|
114
|
+
# will have {:controller=>"photos", :action=>"show", :[id=>/](A-Z){5}/} as
|
|
115
|
+
# requirements.
|
|
93
116
|
def requirements
|
|
94
117
|
@defaults.merge(path.requirements).delete_if { |_, v|
|
|
95
118
|
/.+?/m == v
|
|
@@ -144,21 +167,23 @@ module ActionDispatch
|
|
|
144
167
|
end
|
|
145
168
|
|
|
146
169
|
def matches?(request)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
+
)
|
|
162
187
|
end
|
|
163
188
|
|
|
164
189
|
def ip
|
|
@@ -166,21 +191,12 @@ module ActionDispatch
|
|
|
166
191
|
end
|
|
167
192
|
|
|
168
193
|
def requires_matching_verb?
|
|
169
|
-
|
|
194
|
+
@request_method_match != VerbMatchers::All
|
|
170
195
|
end
|
|
171
196
|
|
|
172
197
|
def verb
|
|
173
|
-
|
|
198
|
+
@request_method_match.verb
|
|
174
199
|
end
|
|
175
|
-
|
|
176
|
-
private
|
|
177
|
-
def verbs
|
|
178
|
-
@request_method_match.map(&:verb)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def match_verb(request)
|
|
182
|
-
@request_method_match.any? { |m| m.call request }
|
|
183
|
-
end
|
|
184
200
|
end
|
|
185
201
|
end
|
|
186
202
|
# :startdoc:
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionDispatch
|
|
4
6
|
module Journey # :nodoc:
|
|
5
7
|
class Router # :nodoc:
|
|
6
8
|
class Utils # :nodoc:
|
|
7
9
|
# Normalizes URI path.
|
|
8
10
|
#
|
|
9
|
-
# Strips off trailing slash and ensures there is a leading slash.
|
|
10
|
-
#
|
|
11
|
+
# Strips off trailing slash and ensures there is a leading slash. Also converts
|
|
12
|
+
# downcase URL encoded string to uppercase.
|
|
11
13
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
14
|
+
# normalize_path("/foo") # => "/foo"
|
|
15
|
+
# normalize_path("/foo/") # => "/foo"
|
|
16
|
+
# normalize_path("foo") # => "/foo"
|
|
17
|
+
# normalize_path("") # => "/"
|
|
18
|
+
# normalize_path("/%ab") # => "/%AB"
|
|
17
19
|
def self.normalize_path(path)
|
|
18
|
-
|
|
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
|
|
19
28
|
encoding = path.encoding
|
|
20
29
|
path = +"/#{path}"
|
|
21
30
|
path.squeeze!("/")
|
|
@@ -28,8 +37,7 @@ module ActionDispatch
|
|
|
28
37
|
path.force_encoding(encoding)
|
|
29
38
|
end
|
|
30
39
|
|
|
31
|
-
# URI path and fragment escaping
|
|
32
|
-
# https://tools.ietf.org/html/rfc3986
|
|
40
|
+
# URI path and fragment escaping https://tools.ietf.org/html/rfc3986
|
|
33
41
|
class UriEncoder # :nodoc:
|
|
34
42
|
ENCODE = "%%%02X"
|
|
35
43
|
US_ASCII = Encoding::US_ASCII
|
|
@@ -42,11 +50,11 @@ module ActionDispatch
|
|
|
42
50
|
UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~"
|
|
43
51
|
SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;="
|
|
44
52
|
|
|
45
|
-
ESCAPED = /%[a-zA-Z0-9]{2}
|
|
53
|
+
ESCAPED = /%[a-zA-Z0-9]{2}/
|
|
46
54
|
|
|
47
|
-
FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/?]
|
|
48
|
-
SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]
|
|
49
|
-
PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]
|
|
55
|
+
FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/?]/
|
|
56
|
+
SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/
|
|
57
|
+
PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/
|
|
50
58
|
|
|
51
59
|
def escape_fragment(fragment)
|
|
52
60
|
escape(fragment, FRAGMENT)
|
|
@@ -60,11 +68,6 @@ module ActionDispatch
|
|
|
60
68
|
escape(segment, SEGMENT)
|
|
61
69
|
end
|
|
62
70
|
|
|
63
|
-
def unescape_uri(uri)
|
|
64
|
-
encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
|
|
65
|
-
uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
71
|
private
|
|
69
72
|
def escape(component, pattern)
|
|
70
73
|
component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
|
|
@@ -90,14 +93,6 @@ module ActionDispatch
|
|
|
90
93
|
def self.escape_fragment(fragment)
|
|
91
94
|
ENCODER.escape_fragment(fragment.to_s)
|
|
92
95
|
end
|
|
93
|
-
|
|
94
|
-
# Replaces any escaped sequences with their unescaped representations.
|
|
95
|
-
#
|
|
96
|
-
# uri = "/topics?title=Ruby%20on%20Rails"
|
|
97
|
-
# unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
|
|
98
|
-
def self.unescape_uri(uri)
|
|
99
|
-
ENCODER.unescape_uri(uri)
|
|
100
|
-
end
|
|
101
96
|
end
|
|
102
97
|
end
|
|
103
98
|
end
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
require "cgi/escape"
|
|
6
|
+
require "cgi/util" if RUBY_VERSION < "3.5"
|
|
3
7
|
require "action_dispatch/journey/router/utils"
|
|
4
8
|
require "action_dispatch/journey/routes"
|
|
5
9
|
require "action_dispatch/journey/formatter"
|
|
6
|
-
|
|
7
|
-
before = $-w
|
|
8
|
-
$-w = false
|
|
9
10
|
require "action_dispatch/journey/parser"
|
|
10
|
-
$-w = before
|
|
11
|
-
|
|
12
11
|
require "action_dispatch/journey/route"
|
|
13
12
|
require "action_dispatch/journey/path/pattern"
|
|
14
13
|
|
|
@@ -22,78 +21,85 @@ module ActionDispatch
|
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
def eager_load!
|
|
25
|
-
# Eagerly trigger the simulator's initialization so
|
|
26
|
-
#
|
|
24
|
+
# Eagerly trigger the simulator's initialization so it doesn't happen during a
|
|
25
|
+
# request cycle.
|
|
27
26
|
simulator
|
|
28
27
|
nil
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
def serve(req)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
script_name = req.script_name
|
|
36
|
-
|
|
37
|
-
unless route.path.anchored
|
|
38
|
-
req.script_name = (script_name.to_s + match.to_s).chomp("/")
|
|
39
|
-
req.path_info = match.post_match
|
|
40
|
-
req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
tmp_params = set_params.merge route.defaults
|
|
44
|
-
parameters.each_pair { |key, val|
|
|
45
|
-
tmp_params[key] = val.force_encoding(::Encoding::UTF_8)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
req.path_parameters = tmp_params
|
|
49
|
-
req.route_uri_pattern = route.path.spec.to_s
|
|
31
|
+
recognize(req) do |route, parameters|
|
|
32
|
+
req.path_parameters = parameters
|
|
33
|
+
req.route = route
|
|
50
34
|
|
|
51
35
|
_, headers, _ = response = route.app.serve(req)
|
|
52
36
|
|
|
53
|
-
|
|
54
|
-
req.script_name = script_name
|
|
55
|
-
req.path_info = path_info
|
|
56
|
-
req.path_parameters = set_params
|
|
57
|
-
next
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
return response
|
|
37
|
+
return response unless headers[Constants::X_CASCADE] == "pass"
|
|
61
38
|
end
|
|
62
39
|
|
|
63
40
|
[404, { Constants::X_CASCADE => "pass" }, ["Not Found"]]
|
|
64
41
|
end
|
|
65
42
|
|
|
66
|
-
def recognize(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
rails_req.path_info = match.post_match
|
|
71
|
-
rails_req.path_info = "/" + rails_req.path_info unless rails_req.path_info.start_with? "/"
|
|
72
|
-
end
|
|
43
|
+
def recognize(req, &block)
|
|
44
|
+
req_params = req.path_parameters
|
|
45
|
+
path_info = req.path_info
|
|
46
|
+
script_name = req.script_name
|
|
73
47
|
|
|
74
|
-
|
|
75
|
-
yield(route, parameters)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
48
|
+
routes = filter_routes(path_info)
|
|
78
49
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
asts = groups.values.map(&:first)
|
|
83
|
-
tt.visualizer(asts)
|
|
84
|
-
end
|
|
50
|
+
custom_routes.each { |r|
|
|
51
|
+
routes << r if r.path.match?(path_info)
|
|
52
|
+
}
|
|
85
53
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
54
|
+
if req.head?
|
|
55
|
+
routes = match_head_routes(routes, req)
|
|
56
|
+
else
|
|
57
|
+
routes.select! { |r| r.matches?(req) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if routes.size > 1
|
|
61
|
+
routes.sort! do |a, b|
|
|
62
|
+
a.precedence <=> b.precedence
|
|
63
|
+
end
|
|
91
64
|
end
|
|
92
65
|
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
95
99
|
end
|
|
100
|
+
end
|
|
96
101
|
|
|
102
|
+
private
|
|
97
103
|
def simulator
|
|
98
104
|
routes.simulator
|
|
99
105
|
end
|
|
@@ -103,35 +109,9 @@ module ActionDispatch
|
|
|
103
109
|
end
|
|
104
110
|
|
|
105
111
|
def filter_routes(path)
|
|
106
|
-
return [] unless ast
|
|
107
112
|
simulator.memos(path) { [] }
|
|
108
113
|
end
|
|
109
114
|
|
|
110
|
-
def find_routes(req)
|
|
111
|
-
path_info = req.path_info
|
|
112
|
-
routes = filter_routes(path_info).concat custom_routes.find_all { |r|
|
|
113
|
-
r.path.match?(path_info)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if req.head?
|
|
117
|
-
routes = match_head_routes(routes, req)
|
|
118
|
-
else
|
|
119
|
-
routes.select! { |r| r.matches?(req) }
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
routes.sort_by!(&:precedence)
|
|
123
|
-
|
|
124
|
-
routes.each { |r|
|
|
125
|
-
match_data = r.path.match(path_info)
|
|
126
|
-
path_parameters = {}
|
|
127
|
-
match_data.names.each_with_index { |name, i|
|
|
128
|
-
val = match_data[i + 1]
|
|
129
|
-
path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
|
|
130
|
-
}
|
|
131
|
-
yield [match_data, path_parameters, r]
|
|
132
|
-
}
|
|
133
|
-
end
|
|
134
|
-
|
|
135
115
|
def match_head_routes(routes, req)
|
|
136
116
|
head_routes = routes.select { |r| r.requires_matching_verb? && r.matches?(req) }
|
|
137
117
|
return head_routes unless head_routes.empty?
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionDispatch
|
|
4
6
|
module Journey # :nodoc:
|
|
5
|
-
# The Routing table. Contains all routes for a system. Routes can be
|
|
6
|
-
#
|
|
7
|
+
# The Routing table. Contains all routes for a system. Routes can be added to
|
|
8
|
+
# the table by calling Routes#add_route.
|
|
7
9
|
class Routes # :nodoc:
|
|
8
10
|
include Enumerable
|
|
9
11
|
|
|
@@ -70,6 +72,13 @@ module ActionDispatch
|
|
|
70
72
|
route
|
|
71
73
|
end
|
|
72
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
|
+
|
|
73
82
|
private
|
|
74
83
|
def clear_cache!
|
|
75
84
|
@ast = nil
|
|
@@ -1,70 +1,74 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
require "strscan"
|
|
4
6
|
|
|
5
7
|
module ActionDispatch
|
|
6
8
|
module Journey # :nodoc:
|
|
7
9
|
class Scanner # :nodoc:
|
|
10
|
+
STATIC_TOKENS = Array.new(150)
|
|
11
|
+
STATIC_TOKENS[".".ord] = :DOT
|
|
12
|
+
STATIC_TOKENS["/".ord] = :SLASH
|
|
13
|
+
STATIC_TOKENS["(".ord] = :LPAREN
|
|
14
|
+
STATIC_TOKENS[")".ord] = :RPAREN
|
|
15
|
+
STATIC_TOKENS["|".ord] = :OR
|
|
16
|
+
STATIC_TOKENS[":".ord] = :SYMBOL
|
|
17
|
+
STATIC_TOKENS["*".ord] = :STAR
|
|
18
|
+
STATIC_TOKENS.freeze
|
|
19
|
+
|
|
20
|
+
class Scanner < StringScanner
|
|
21
|
+
unless method_defined?(:peek_byte) # https://github.com/ruby/strscan/pull/89
|
|
22
|
+
def peek_byte
|
|
23
|
+
string.getbyte(pos)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
8
28
|
def initialize
|
|
9
|
-
@
|
|
29
|
+
@scanner = nil
|
|
30
|
+
@length = nil
|
|
10
31
|
end
|
|
11
32
|
|
|
12
33
|
def scan_setup(str)
|
|
13
|
-
@
|
|
34
|
+
@scanner = Scanner.new(str)
|
|
14
35
|
end
|
|
15
36
|
|
|
16
|
-
def
|
|
17
|
-
@
|
|
18
|
-
end
|
|
37
|
+
def next_token
|
|
38
|
+
return if @scanner.eos?
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
until token = scan || @scanner.eos?; end
|
|
41
|
+
token
|
|
22
42
|
end
|
|
23
43
|
|
|
24
|
-
def
|
|
25
|
-
@
|
|
44
|
+
def last_string
|
|
45
|
+
-@scanner.string.byteslice(@scanner.pos - @length, @length)
|
|
26
46
|
end
|
|
27
47
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
token
|
|
48
|
+
def last_literal
|
|
49
|
+
last_str = @scanner.string.byteslice(@scanner.pos - @length, @length)
|
|
50
|
+
last_str.tr! "\\", ""
|
|
51
|
+
-last_str
|
|
33
52
|
end
|
|
34
53
|
|
|
35
54
|
private
|
|
36
|
-
# takes advantage of String @- deduping capabilities in Ruby 2.5 upwards
|
|
37
|
-
# see: https://bugs.ruby-lang.org/issues/13077
|
|
38
|
-
def dedup_scan(regex)
|
|
39
|
-
r = @ss.scan(regex)
|
|
40
|
-
r ? -r : nil
|
|
41
|
-
end
|
|
42
|
-
|
|
43
55
|
def scan
|
|
56
|
+
next_byte = @scanner.peek_byte
|
|
44
57
|
case
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
[:OR, "|"]
|
|
54
|
-
when @ss.skip(/\./)
|
|
55
|
-
[:DOT, "."]
|
|
56
|
-
when text = dedup_scan(/:\w+/)
|
|
57
|
-
[:SYMBOL, text]
|
|
58
|
-
when text = dedup_scan(/\*\w+/)
|
|
59
|
-
[:STAR, text]
|
|
60
|
-
when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
|
|
61
|
-
text.tr! "\\", ""
|
|
62
|
-
[:LITERAL, -text]
|
|
63
|
-
# any char
|
|
64
|
-
when text = dedup_scan(/./)
|
|
65
|
-
[:LITERAL, text]
|
|
58
|
+
when (token = STATIC_TOKENS[next_byte]) && (token != :SYMBOL || next_byte_is_not_a_token?)
|
|
59
|
+
@scanner.pos += 1
|
|
60
|
+
@length = @scanner.skip(/\w+/).to_i + 1 if token == :SYMBOL || token == :STAR
|
|
61
|
+
token
|
|
62
|
+
when @length = @scanner.skip(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
|
|
63
|
+
:LITERAL
|
|
64
|
+
when @length = @scanner.skip(/./)
|
|
65
|
+
:LITERAL
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
|
+
|
|
69
|
+
def next_byte_is_not_a_token?
|
|
70
|
+
!STATIC_TOKENS[@scanner.string.getbyte(@scanner.pos + 1)]
|
|
71
|
+
end
|
|
68
72
|
end
|
|
69
73
|
end
|
|
70
74
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
3
5
|
module ActionDispatch
|
|
4
6
|
# :stopdoc:
|
|
5
7
|
module Journey
|
|
@@ -126,8 +128,8 @@ module ActionDispatch
|
|
|
126
128
|
def visit_DOT(n, seed); terminal(n, seed); end
|
|
127
129
|
|
|
128
130
|
instance_methods(false).each do |pim|
|
|
129
|
-
next unless pim
|
|
130
|
-
DISPATCH_CACHE[
|
|
131
|
+
next unless pim.start_with?("visit_")
|
|
132
|
+
DISPATCH_CACHE[pim.name.delete_prefix("visit_").to_sym] = pim
|
|
131
133
|
end
|
|
132
134
|
end
|
|
133
135
|
|
|
@@ -165,32 +167,64 @@ module ActionDispatch
|
|
|
165
167
|
INSTANCE = new
|
|
166
168
|
end
|
|
167
169
|
|
|
168
|
-
class String
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
175
186
|
last_child = node.children.last
|
|
176
|
-
node.children.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def visit_GROUP(node, seed)
|
|
188
|
-
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}"
|
|
189
196
|
end
|
|
197
|
+
end
|
|
190
198
|
|
|
191
|
-
|
|
199
|
+
INSTANCE = new
|
|
192
200
|
end
|
|
193
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
|
+
|
|
194
228
|
class Dot < FunctionalVisitor # :nodoc:
|
|
195
229
|
def initialize
|
|
196
230
|
@nodes = []
|