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
@@ -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)
|
@@ -55,6 +55,17 @@ module ActionDispatch
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
def editor_url(location, line: nil)
|
59
|
+
if editor = ActiveSupport::Editor.current
|
60
|
+
line ||= location&.lineno
|
61
|
+
absolute_path = location&.absolute_path
|
62
|
+
|
63
|
+
if absolute_path && line && File.exist?(absolute_path)
|
64
|
+
editor.url_for(absolute_path, line)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
58
69
|
def protect_against_forgery?
|
59
70
|
false
|
60
71
|
end
|
@@ -23,6 +23,7 @@ module ActionDispatch
|
|
23
23
|
"ActionDispatch::Http::Parameters::ParseError" => :bad_request,
|
24
24
|
"ActionController::BadRequest" => :bad_request,
|
25
25
|
"ActionController::ParameterMissing" => :bad_request,
|
26
|
+
"ActionController::TooManyRequests" => :too_many_requests,
|
26
27
|
"Rack::QueryParser::ParameterTypeError" => :bad_request,
|
27
28
|
"Rack::QueryParser::InvalidParameterError" => :bad_request
|
28
29
|
)
|
@@ -148,15 +149,20 @@ module ActionDispatch
|
|
148
149
|
application_trace_with_ids = []
|
149
150
|
framework_trace_with_ids = []
|
150
151
|
full_trace_with_ids = []
|
152
|
+
application_traces = application_trace.map(&:to_s)
|
151
153
|
|
154
|
+
full_trace = backtrace_cleaner&.clean_locations(backtrace, :all).presence || backtrace
|
152
155
|
full_trace.each_with_index do |trace, idx|
|
156
|
+
filtered_trace = backtrace_cleaner&.clean_frame(trace, :all) || trace
|
157
|
+
|
153
158
|
trace_with_id = {
|
154
159
|
exception_object_id: @exception.object_id,
|
155
160
|
id: idx,
|
156
|
-
trace: trace
|
161
|
+
trace: trace,
|
162
|
+
filtered_trace: filtered_trace,
|
157
163
|
}
|
158
164
|
|
159
|
-
if
|
165
|
+
if application_traces.include?(filtered_trace.to_s)
|
160
166
|
application_trace_with_ids << trace_with_id
|
161
167
|
else
|
162
168
|
framework_trace_with_ids << trace_with_id
|
@@ -197,7 +203,7 @@ module ActionDispatch
|
|
197
203
|
|
198
204
|
def source_extracts
|
199
205
|
backtrace.map do |trace|
|
200
|
-
extract_source(trace)
|
206
|
+
extract_source(trace).merge(trace: trace)
|
201
207
|
end
|
202
208
|
end
|
203
209
|
|
@@ -261,13 +267,13 @@ module ActionDispatch
|
|
261
267
|
end
|
262
268
|
|
263
269
|
(@exception.backtrace_locations || []).map do |loc|
|
264
|
-
if built_methods.key?(loc.
|
270
|
+
if built_methods.key?(loc.base_label)
|
265
271
|
thread_backtrace_location = if loc.respond_to?(:__getobj__)
|
266
272
|
loc.__getobj__
|
267
273
|
else
|
268
274
|
loc
|
269
275
|
end
|
270
|
-
SourceMapLocation.new(thread_backtrace_location, built_methods[loc.
|
276
|
+
SourceMapLocation.new(thread_backtrace_location, built_methods[loc.base_label])
|
271
277
|
else
|
272
278
|
loc
|
273
279
|
end
|
@@ -12,6 +12,10 @@ module ActionDispatch
|
|
12
12
|
|
13
13
|
def call(env)
|
14
14
|
state = @executor.run!(reset: true)
|
15
|
+
if response_finished = env["rack.response_finished"]
|
16
|
+
response_finished << proc { state.complete! }
|
17
|
+
end
|
18
|
+
|
15
19
|
begin
|
16
20
|
response = @app.call(env)
|
17
21
|
|
@@ -20,7 +24,11 @@ module ActionDispatch
|
|
20
24
|
@executor.error_reporter.report(error, handled: false, source: "application.action_dispatch")
|
21
25
|
end
|
22
26
|
|
23
|
-
|
27
|
+
unless response_finished
|
28
|
+
response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
|
29
|
+
end
|
30
|
+
returned = true
|
31
|
+
response
|
24
32
|
rescue Exception => error
|
25
33
|
request = ActionDispatch::Request.new env
|
26
34
|
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
@@ -28,7 +36,9 @@ module ActionDispatch
|
|
28
36
|
@executor.error_reporter.report(wrapper.unwrapped_exception, handled: false, source: "application.action_dispatch")
|
29
37
|
raise
|
30
38
|
ensure
|
31
|
-
|
39
|
+
if !returned && !response_finished
|
40
|
+
state.complete!
|
41
|
+
end
|
32
42
|
end
|
33
43
|
end
|
34
44
|
end
|
@@ -25,11 +25,7 @@ module ActionDispatch
|
|
25
25
|
def call(env)
|
26
26
|
request = ActionDispatch::Request.new(env)
|
27
27
|
status = request.path_info[1..-1].to_i
|
28
|
-
|
29
|
-
content_type = request.formats.first
|
30
|
-
rescue ActionDispatch::Http::MimeNegotiation::InvalidType
|
31
|
-
content_type = Mime[:text]
|
32
|
-
end
|
28
|
+
content_type = request.formats.first
|
33
29
|
body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
|
34
30
|
|
35
31
|
if env["action_dispatch.original_request_method"] == "HEAD"
|
@@ -18,11 +18,16 @@ module ActionDispatch
|
|
18
18
|
# * `expire_after` - The length of time a session will be stored before
|
19
19
|
# automatically expiring. By default, the `:expires_in` option of the cache
|
20
20
|
# is used.
|
21
|
+
# * `check_collisions` - Check if newly generated session ids aren't already in use.
|
22
|
+
# If for some reason 128 bits of randomness aren't considered secure enough to avoid
|
23
|
+
# collisions, this option can be enabled to ensure newly generated ids aren't in use.
|
24
|
+
# By default, it is set to `false` to avoid additional cache write operations.
|
21
25
|
#
|
22
26
|
class CacheStore < AbstractSecureStore
|
23
27
|
def initialize(app, options = {})
|
24
28
|
@cache = options[:cache] || Rails.cache
|
25
29
|
options[:expire_after] ||= @cache.options[:expires_in]
|
30
|
+
@check_collisions = options[:check_collisions] || false
|
26
31
|
super
|
27
32
|
end
|
28
33
|
|
@@ -61,6 +66,18 @@ module ActionDispatch
|
|
61
66
|
def get_session_with_fallback(sid)
|
62
67
|
@cache.read(cache_key(sid.private_id)) || @cache.read(cache_key(sid.public_id))
|
63
68
|
end
|
69
|
+
|
70
|
+
def generate_sid
|
71
|
+
if @check_collisions
|
72
|
+
loop do
|
73
|
+
sid = super
|
74
|
+
key = cache_key(sid.private_id)
|
75
|
+
break sid if @cache.write(key, {}, unless_exist: true, expires_in: default_options[:expire_after])
|
76
|
+
end
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
64
81
|
end
|
65
82
|
end
|
66
83
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<button onclick="copyAsText.bind(this)()">Copy as text</button>
|