actionpack 8.0.2 → 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 +252 -137
- data/README.rdoc +1 -1
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +11 -14
- data/lib/abstract_controller/caching.rb +6 -3
- data/lib/abstract_controller/collector.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/live.rb +0 -6
- 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 +63 -7
- data/lib/action_controller/metal/renderers.rb +27 -6
- data/lib/action_controller/metal/rendering.rb +8 -2
- 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_controller/renderer.rb +0 -1
- data/lib/action_dispatch/constants.rb +6 -0
- data/lib/action_dispatch/http/cache.rb +111 -1
- data/lib/action_dispatch/http/content_security_policy.rb +13 -1
- data/lib/action_dispatch/http/filter_parameters.rb +5 -3
- data/lib/action_dispatch/http/mime_negotiation.rb +8 -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 +65 -17
- data/lib/action_dispatch/http/url.rb +101 -5
- data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
- data/lib/action_dispatch/journey/gtg/transition_table.rb +29 -39
- 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 +10 -2
- data/lib/action_dispatch/middleware/debug_view.rb +11 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +14 -8
- data/lib/action_dispatch/middleware/executor.rb +12 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -6
- 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 +3 -6
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +14 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +11 -3
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +13 -12
@@ -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)
|
@@ -272,7 +368,7 @@ module ActionDispatch
|
|
272
368
|
end
|
273
369
|
end
|
274
370
|
|
275
|
-
# Returns whether this request is using the standard port
|
371
|
+
# Returns whether this request is using the standard port.
|
276
372
|
#
|
277
373
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
278
374
|
# req.standard_port? # => true
|
@@ -307,7 +403,7 @@ module ActionDispatch
|
|
307
403
|
standard_port? ? "" : ":#{port}"
|
308
404
|
end
|
309
405
|
|
310
|
-
# Returns the requested port, such as 8080, based on SERVER_PORT
|
406
|
+
# Returns the requested port, such as 8080, based on SERVER_PORT.
|
311
407
|
#
|
312
408
|
# req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
|
313
409
|
# req.server_port # => 80
|
@@ -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
|
@@ -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
|