actionpack 8.0.3 → 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 +243 -168
- 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/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 +3 -2
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +11 -10
@@ -17,9 +17,11 @@ module ActionDispatch
|
|
17
17
|
# For more information about filter behavior, see
|
18
18
|
# ActiveSupport::ParameterFilter.
|
19
19
|
module FilterParameters
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
# :stopdoc:
|
21
|
+
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"]
|
22
|
+
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new
|
23
|
+
NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH
|
24
|
+
# :startdoc:
|
23
25
|
|
24
26
|
def initialize
|
25
27
|
super
|
@@ -13,6 +13,7 @@ Mime::Type.register "text/calendar", :ics
|
|
13
13
|
Mime::Type.register "text/csv", :csv
|
14
14
|
Mime::Type.register "text/vcard", :vcf
|
15
15
|
Mime::Type.register "text/vtt", :vtt, %w(vtt)
|
16
|
+
Mime::Type.register "text/markdown", :md, [], %w(md markdown)
|
16
17
|
|
17
18
|
Mime::Type.register "image/png", :png, [], %w(png)
|
18
19
|
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
|
@@ -16,15 +16,27 @@ module ActionDispatch
|
|
16
16
|
@param_depth_limit = param_depth_limit
|
17
17
|
end
|
18
18
|
|
19
|
-
cattr_accessor :ignore_leading_brackets
|
20
|
-
|
21
|
-
LEADING_BRACKETS_COMPAT = defined?(::Rack::RELEASE) && ::Rack::RELEASE.to_s.start_with?("2.")
|
22
|
-
|
23
19
|
cattr_accessor :default
|
24
20
|
self.default = make_default(100)
|
25
21
|
|
26
22
|
class << self
|
27
23
|
delegate :from_query_string, :from_pairs, :from_hash, to: :default
|
24
|
+
|
25
|
+
def ignore_leading_brackets
|
26
|
+
ActionDispatch.deprecator.warn <<~MSG
|
27
|
+
ActionDispatch::ParamBuilder.ignore_leading_brackets is deprecated and have no effect and will be removed in Rails 8.2.
|
28
|
+
MSG
|
29
|
+
|
30
|
+
@ignore_leading_brackets
|
31
|
+
end
|
32
|
+
|
33
|
+
def ignore_leading_brackets=(value)
|
34
|
+
ActionDispatch.deprecator.warn <<~MSG
|
35
|
+
ActionDispatch::ParamBuilder.ignore_leading_brackets is deprecated and have no effect and will be removed in Rails 8.2.
|
36
|
+
MSG
|
37
|
+
|
38
|
+
@ignore_leading_brackets = value
|
39
|
+
end
|
28
40
|
end
|
29
41
|
|
30
42
|
def from_query_string(qs, separator: nil, encoding_template: nil)
|
@@ -69,30 +81,15 @@ module ActionDispatch
|
|
69
81
|
# nil name, treat same as empty string (required by tests)
|
70
82
|
k = after = ""
|
71
83
|
elsif depth == 0
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
if !ignore_leading_brackets && (k != $& || !after.empty? && !after.start_with?("["))
|
79
|
-
ActionDispatch.deprecator.warn("Skipping over leading brackets in parameter name #{name.inspect} is deprecated and will parse differently in Rails 8.1 or Rack 3.0.")
|
80
|
-
end
|
81
|
-
else
|
82
|
-
k = name
|
83
|
-
after = ""
|
84
|
-
end
|
84
|
+
# Start of parsing, don't treat [] or [ at start of string specially
|
85
|
+
if start = name.index("[", 1)
|
86
|
+
# Start of parameter nesting, use part before brackets as key
|
87
|
+
k = name[0, start]
|
88
|
+
after = name[start, name.length]
|
85
89
|
else
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
k = name[0, start]
|
90
|
-
after = name[start, name.length]
|
91
|
-
else
|
92
|
-
# Plain parameter with no nesting
|
93
|
-
k = name
|
94
|
-
after = ""
|
95
|
-
end
|
90
|
+
# Plain parameter with no nesting
|
91
|
+
k = name
|
92
|
+
after = ""
|
96
93
|
end
|
97
94
|
elsif name.start_with?("[]")
|
98
95
|
# Array nesting
|
@@ -111,6 +108,10 @@ module ActionDispatch
|
|
111
108
|
|
112
109
|
return if k.empty?
|
113
110
|
|
111
|
+
unless k.valid_encoding?
|
112
|
+
raise InvalidParameterError, "Invalid encoding for parameter: #{k}"
|
113
|
+
end
|
114
|
+
|
114
115
|
if depth == 0 && String === v
|
115
116
|
# We have to wait until we've found the top part of the name,
|
116
117
|
# because that's what the encoding template is configured with
|
@@ -65,14 +65,14 @@ module ActionDispatch
|
|
65
65
|
alias :params :parameters
|
66
66
|
|
67
67
|
def path_parameters=(parameters) # :nodoc:
|
68
|
-
|
68
|
+
@env.delete("action_dispatch.request.parameters")
|
69
69
|
|
70
70
|
parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action])
|
71
71
|
# If any of the path parameters has an invalid encoding then raise since it's
|
72
72
|
# likely to trigger errors further on.
|
73
73
|
Request::Utils.check_param_encoding(parameters)
|
74
74
|
|
75
|
-
|
75
|
+
@env[PARAMETERS_KEY] = parameters
|
76
76
|
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
|
77
77
|
raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}")
|
78
78
|
end
|
@@ -82,7 +82,7 @@ module ActionDispatch
|
|
82
82
|
#
|
83
83
|
# { action: "my_action", controller: "my_controller" }
|
84
84
|
def path_parameters
|
85
|
-
|
85
|
+
@env[PARAMETERS_KEY] ||= {}
|
86
86
|
end
|
87
87
|
|
88
88
|
private
|
@@ -6,12 +6,21 @@ require "rack"
|
|
6
6
|
module ActionDispatch
|
7
7
|
class QueryParser
|
8
8
|
DEFAULT_SEP = /& */n
|
9
|
-
COMPAT_SEP = /[&;] */n
|
10
9
|
COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n, "&;" => /[&;] */n }
|
11
10
|
|
12
|
-
|
11
|
+
def self.strict_query_string_separator
|
12
|
+
ActionDispatch.deprecator.warn <<~MSG
|
13
|
+
The `strict_query_string_separator` configuration is deprecated have no effect and will be removed in Rails 8.2.
|
14
|
+
MSG
|
15
|
+
@strict_query_string_separator
|
16
|
+
end
|
13
17
|
|
14
|
-
|
18
|
+
def self.strict_query_string_separator=(value)
|
19
|
+
ActionDispatch.deprecator.warn <<~MSG
|
20
|
+
The `strict_query_string_separator` configuration is deprecated have no effect and will be removed in Rails 8.2.
|
21
|
+
MSG
|
22
|
+
@strict_query_string_separator = value
|
23
|
+
end
|
15
24
|
|
16
25
|
#--
|
17
26
|
# Note this departs from WHATWG's specified parsing algorithm by
|
@@ -25,13 +34,6 @@ module ActionDispatch
|
|
25
34
|
splitter =
|
26
35
|
if separator
|
27
36
|
COMMON_SEP[separator] || /[#{separator}] */n
|
28
|
-
elsif strict_query_string_separator
|
29
|
-
DEFAULT_SEP
|
30
|
-
elsif SEMICOLON_COMPAT && s.include?(";")
|
31
|
-
if strict_query_string_separator.nil?
|
32
|
-
ActionDispatch.deprecator.warn("Using semicolon as a query string separator is deprecated and will not be supported in Rails 8.1 or Rack 3.0. Use `&` instead.")
|
33
|
-
end
|
34
|
-
COMPAT_SEP
|
35
37
|
else
|
36
38
|
DEFAULT_SEP
|
37
39
|
end
|
@@ -25,7 +25,6 @@ module ActionDispatch
|
|
25
25
|
include ActionDispatch::Http::FilterParameters
|
26
26
|
include ActionDispatch::Http::URL
|
27
27
|
include ActionDispatch::ContentSecurityPolicy::Request
|
28
|
-
include ActionDispatch::PermissionsPolicy::Request
|
29
28
|
include Rack::Request::Env
|
30
29
|
|
31
30
|
autoload :Session, "action_dispatch/request/session"
|
@@ -139,7 +138,7 @@ module ActionDispatch
|
|
139
138
|
|
140
139
|
# Populate the HTTP method lookup cache.
|
141
140
|
HTTP_METHODS.each { |method|
|
142
|
-
HTTP_METHOD_LOOKUP[method] = method.downcase.
|
141
|
+
HTTP_METHOD_LOOKUP[method] = method.downcase.tap { |m| m.tr!("-", "_") }.to_sym
|
143
142
|
}
|
144
143
|
|
145
144
|
alias raw_request_method request_method # :nodoc:
|
@@ -158,11 +157,17 @@ module ActionDispatch
|
|
158
157
|
#
|
159
158
|
# request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
|
160
159
|
def route_uri_pattern
|
161
|
-
get_header("action_dispatch.route_uri_pattern")
|
160
|
+
unless pattern = get_header("action_dispatch.route_uri_pattern")
|
161
|
+
route = get_header("action_dispatch.route")
|
162
|
+
return if route.nil?
|
163
|
+
pattern = route.path.spec.to_s
|
164
|
+
set_header("action_dispatch.route_uri_pattern", pattern)
|
165
|
+
end
|
166
|
+
pattern
|
162
167
|
end
|
163
168
|
|
164
|
-
def
|
165
|
-
|
169
|
+
def route=(route) # :nodoc:
|
170
|
+
@env["action_dispatch.route"] = route
|
166
171
|
end
|
167
172
|
|
168
173
|
def routes # :nodoc:
|
@@ -277,14 +277,27 @@ module ActionDispatch # :nodoc:
|
|
277
277
|
# Sets the HTTP response's content MIME type. For example, in the controller you
|
278
278
|
# could write this:
|
279
279
|
#
|
280
|
-
# response.content_type = "text/
|
280
|
+
# response.content_type = "text/html"
|
281
|
+
#
|
282
|
+
# This method also accepts a symbol with the extension of the MIME type:
|
283
|
+
#
|
284
|
+
# response.content_type = :html
|
281
285
|
#
|
282
286
|
# If a character set has been defined for this response (see #charset=) then the
|
283
287
|
# character set information will also be included in the content type
|
284
288
|
# information.
|
285
289
|
def content_type=(content_type)
|
286
|
-
|
287
|
-
|
290
|
+
case content_type
|
291
|
+
when NilClass
|
292
|
+
return
|
293
|
+
when Symbol
|
294
|
+
mime_type = Mime[content_type]
|
295
|
+
raise ArgumentError, "Unknown MIME type #{content_type}" unless mime_type
|
296
|
+
new_header_info = ContentTypeHeader.new(mime_type.to_s)
|
297
|
+
else
|
298
|
+
new_header_info = parse_content_type(content_type.to_s)
|
299
|
+
end
|
300
|
+
|
288
301
|
prev_header_info = parsed_content_type_header
|
289
302
|
charset = new_header_info.charset || prev_header_info.charset
|
290
303
|
charset ||= self.class.default_charset unless prev_header_info.mime_type
|
@@ -11,8 +11,105 @@ module ActionDispatch
|
|
11
11
|
HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
|
12
12
|
PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
|
13
13
|
|
14
|
+
# DomainExtractor provides utility methods for extracting domain and subdomain
|
15
|
+
# information from host strings. This module is used internally by Action Dispatch
|
16
|
+
# to parse host names and separate the domain from subdomains based on the
|
17
|
+
# top-level domain (TLD) length.
|
18
|
+
#
|
19
|
+
# The module assumes a standard domain structure where domains consist of:
|
20
|
+
# - Subdomains (optional, can be multiple levels)
|
21
|
+
# - Domain name
|
22
|
+
# - Top-level domain (TLD, can be multiple levels like .co.uk)
|
23
|
+
#
|
24
|
+
# For example, in "api.staging.example.co.uk":
|
25
|
+
# - Subdomains: ["api", "staging"]
|
26
|
+
# - Domain: "example.co.uk" (with tld_length=2)
|
27
|
+
# - TLD: "co.uk"
|
28
|
+
module DomainExtractor
|
29
|
+
extend self
|
30
|
+
|
31
|
+
# Extracts the domain part from a host string, including the specified
|
32
|
+
# number of top-level domain components.
|
33
|
+
#
|
34
|
+
# The domain includes the main domain name plus the TLD components.
|
35
|
+
# The +tld_length+ parameter specifies how many components from the right
|
36
|
+
# should be considered part of the TLD.
|
37
|
+
#
|
38
|
+
# ==== Parameters
|
39
|
+
#
|
40
|
+
# [+host+]
|
41
|
+
# The host string to extract the domain from.
|
42
|
+
#
|
43
|
+
# [+tld_length+]
|
44
|
+
# The number of domain components that make up the TLD. For example,
|
45
|
+
# use 1 for ".com" or 2 for ".co.uk".
|
46
|
+
#
|
47
|
+
# ==== Examples
|
48
|
+
#
|
49
|
+
# # Standard TLD (tld_length = 1)
|
50
|
+
# DomainExtractor.domain_from("www.example.com", 1)
|
51
|
+
# # => "example.com"
|
52
|
+
#
|
53
|
+
# # Country-code TLD (tld_length = 2)
|
54
|
+
# DomainExtractor.domain_from("www.example.co.uk", 2)
|
55
|
+
# # => "example.co.uk"
|
56
|
+
#
|
57
|
+
# # Multiple subdomains
|
58
|
+
# DomainExtractor.domain_from("api.staging.myapp.herokuapp.com", 1)
|
59
|
+
# # => "herokuapp.com"
|
60
|
+
#
|
61
|
+
# # Single component (returns the host itself)
|
62
|
+
# DomainExtractor.domain_from("localhost", 1)
|
63
|
+
# # => "localhost"
|
64
|
+
def domain_from(host, tld_length)
|
65
|
+
host.split(".").last(1 + tld_length).join(".")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Extracts the subdomain components from a host string as an Array.
|
69
|
+
#
|
70
|
+
# Returns all the components that come before the domain and TLD parts.
|
71
|
+
# The +tld_length+ parameter is used to determine where the domain begins
|
72
|
+
# so that everything before it is considered a subdomain.
|
73
|
+
#
|
74
|
+
# ==== Parameters
|
75
|
+
#
|
76
|
+
# [+host+]
|
77
|
+
# The host string to extract subdomains from.
|
78
|
+
#
|
79
|
+
# [+tld_length+]
|
80
|
+
# The number of domain components that make up the TLD. This affects
|
81
|
+
# where the domain boundary is calculated.
|
82
|
+
#
|
83
|
+
# ==== Examples
|
84
|
+
#
|
85
|
+
# # Standard TLD (tld_length = 1)
|
86
|
+
# DomainExtractor.subdomains_from("www.example.com", 1)
|
87
|
+
# # => ["www"]
|
88
|
+
#
|
89
|
+
# # Country-code TLD (tld_length = 2)
|
90
|
+
# DomainExtractor.subdomains_from("api.staging.example.co.uk", 2)
|
91
|
+
# # => ["api", "staging"]
|
92
|
+
#
|
93
|
+
# # No subdomains
|
94
|
+
# DomainExtractor.subdomains_from("example.com", 1)
|
95
|
+
# # => []
|
96
|
+
#
|
97
|
+
# # Single subdomain with complex TLD
|
98
|
+
# DomainExtractor.subdomains_from("www.mysite.co.uk", 2)
|
99
|
+
# # => ["www"]
|
100
|
+
#
|
101
|
+
# # Multiple levels of subdomains
|
102
|
+
# DomainExtractor.subdomains_from("dev.api.staging.example.com", 1)
|
103
|
+
# # => ["dev", "api", "staging"]
|
104
|
+
def subdomains_from(host, tld_length)
|
105
|
+
parts = host.split(".")
|
106
|
+
parts[0..-(tld_length + 2)]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
14
110
|
mattr_accessor :secure_protocol, default: false
|
15
111
|
mattr_accessor :tld_length, default: 1
|
112
|
+
mattr_accessor :domain_extractor, default: DomainExtractor
|
16
113
|
|
17
114
|
class << self
|
18
115
|
# Returns the domain part of a host given the domain level.
|
@@ -96,12 +193,11 @@ module ActionDispatch
|
|
96
193
|
end
|
97
194
|
|
98
195
|
def extract_domain_from(host, tld_length)
|
99
|
-
|
196
|
+
domain_extractor.domain_from(host, tld_length)
|
100
197
|
end
|
101
198
|
|
102
199
|
def extract_subdomains_from(host, tld_length)
|
103
|
-
|
104
|
-
parts[0..-(tld_length + 2)]
|
200
|
+
domain_extractor.subdomains_from(host, tld_length)
|
105
201
|
end
|
106
202
|
|
107
203
|
def build_host_url(host, port, protocol, options, path)
|
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
# :markup: markdown
|
4
4
|
|
5
|
-
require "strscan"
|
6
|
-
|
7
5
|
module ActionDispatch
|
8
6
|
module Journey # :nodoc:
|
9
7
|
module GTG # :nodoc:
|
@@ -16,7 +14,13 @@ module ActionDispatch
|
|
16
14
|
end
|
17
15
|
|
18
16
|
class Simulator # :nodoc:
|
19
|
-
|
17
|
+
STATIC_TOKENS = Array.new(64)
|
18
|
+
STATIC_TOKENS[".".ord] = "."
|
19
|
+
STATIC_TOKENS["/".ord] = "/"
|
20
|
+
STATIC_TOKENS["?".ord] = "?"
|
21
|
+
STATIC_TOKENS.freeze
|
22
|
+
|
23
|
+
INITIAL_STATE = [0, nil].freeze
|
20
24
|
|
21
25
|
attr_reader :tt
|
22
26
|
|
@@ -25,21 +29,38 @@ module ActionDispatch
|
|
25
29
|
end
|
26
30
|
|
27
31
|
def memos(string)
|
28
|
-
input = StringScanner.new(string)
|
29
32
|
state = INITIAL_STATE
|
30
|
-
start_index = 0
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
+
pos = 0
|
35
|
+
eos = string.bytesize
|
36
|
+
|
37
|
+
while pos < eos
|
38
|
+
start_index = pos
|
39
|
+
pos += 1
|
34
40
|
|
35
|
-
|
41
|
+
if (token = STATIC_TOKENS[string.getbyte(start_index)])
|
42
|
+
state = tt.move(state, string, token, start_index, false)
|
43
|
+
else
|
44
|
+
while pos < eos && STATIC_TOKENS[string.getbyte(pos)].nil?
|
45
|
+
pos += 1
|
46
|
+
end
|
36
47
|
|
37
|
-
|
48
|
+
token = string.byteslice(start_index, pos - start_index)
|
49
|
+
state = tt.move(state, string, token, start_index, true)
|
50
|
+
end
|
38
51
|
end
|
39
52
|
|
40
|
-
acceptance_states =
|
41
|
-
|
42
|
-
|
53
|
+
acceptance_states = []
|
54
|
+
states_count = state.size
|
55
|
+
i = 0
|
56
|
+
while i < states_count
|
57
|
+
if state[i + 1].nil?
|
58
|
+
s = state[i]
|
59
|
+
if tt.accepting?(s)
|
60
|
+
acceptance_states.concat(tt.memo(s))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
i += 2
|
43
64
|
end
|
44
65
|
|
45
66
|
acceptance_states.empty? ? yield : acceptance_states
|
@@ -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
|