actionpack 7.2.2.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 +408 -95
- data/README.rdoc +1 -1
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +12 -17
- data/lib/abstract_controller/caching.rb +6 -3
- data/lib/abstract_controller/callbacks.rb +6 -0
- data/lib/abstract_controller/collector.rb +1 -1
- data/lib/abstract_controller/helpers.rb +1 -1
- data/lib/abstract_controller/logger.rb +2 -1
- data/lib/abstract_controller/rendering.rb +0 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/base.rb +3 -2
- data/lib/action_controller/caching.rb +1 -2
- data/lib/action_controller/form_builder.rb +4 -4
- data/lib/action_controller/log_subscriber.rb +22 -3
- data/lib/action_controller/metal/allow_browser.rb +12 -2
- data/lib/action_controller/metal/conditional_get.rb +30 -1
- data/lib/action_controller/metal/data_streaming.rb +5 -5
- 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/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +66 -26
- data/lib/action_controller/metal/params_wrapper.rb +3 -3
- data/lib/action_controller/metal/permissions_policy.rb +9 -0
- data/lib/action_controller/metal/rate_limiting.rb +39 -9
- data/lib/action_controller/metal/redirecting.rb +109 -16
- data/lib/action_controller/metal/renderers.rb +29 -9
- data/lib/action_controller/metal/rendering.rb +8 -2
- data/lib/action_controller/metal/request_forgery_protection.rb +21 -11
- data/lib/action_controller/metal/rescue.rb +9 -0
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +277 -92
- data/lib/action_controller/railtie.rb +33 -15
- data/lib/action_controller/renderer.rb +0 -1
- data/lib/action_controller/structured_event_subscriber.rb +116 -0
- data/lib/action_controller/test_case.rb +12 -2
- data/lib/action_dispatch/constants.rb +6 -0
- data/lib/action_dispatch/http/cache.rb +138 -11
- data/lib/action_dispatch/http/content_security_policy.rb +14 -1
- data/lib/action_dispatch/http/filter_parameters.rb +5 -3
- data/lib/action_dispatch/http/mime_negotiation.rb +63 -4
- data/lib/action_dispatch/http/mime_types.rb +1 -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 +3 -3
- data/lib/action_dispatch/http/permissions_policy.rb +6 -0
- data/lib/action_dispatch/http/query_parser.rb +55 -0
- data/lib/action_dispatch/http/request.rb +73 -23
- data/lib/action_dispatch/http/response.rb +65 -17
- data/lib/action_dispatch/http/url.rb +112 -16
- data/lib/action_dispatch/journey/formatter.rb +8 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
- data/lib/action_dispatch/journey/gtg/transition_table.rb +37 -45
- data/lib/action_dispatch/journey/nodes/node.rb +2 -1
- data/lib/action_dispatch/journey/parser.rb +99 -196
- 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/scanner.rb +44 -42
- data/lib/action_dispatch/journey/visitors.rb +55 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/log_subscriber.rb +7 -3
- data/lib/action_dispatch/middleware/cookies.rb +8 -4
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -5
- data/lib/action_dispatch/middleware/debug_view.rb +11 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +14 -14
- data/lib/action_dispatch/middleware/executor.rb +17 -4
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -6
- data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
- data/lib/action_dispatch/middleware/ssl.rb +13 -3
- 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/railtie.rb +21 -0
- data/lib/action_dispatch/request/session.rb +1 -0
- data/lib/action_dispatch/request/utils.rb +9 -3
- data/lib/action_dispatch/routing/inspector.rb +80 -57
- data/lib/action_dispatch/routing/mapper.rb +409 -228
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
- data/lib/action_dispatch/routing/redirection.rb +10 -7
- data/lib/action_dispatch/routing/route_set.rb +21 -12
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- 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 +26 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
- data/lib/action_dispatch/testing/integration.rb +16 -7
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch/testing/test_process.rb +1 -2
- data/lib/action_dispatch.rb +14 -4
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +19 -38
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
|
@@ -7,66 +7,68 @@ require "strscan"
|
|
|
7
7
|
module ActionDispatch
|
|
8
8
|
module Journey # :nodoc:
|
|
9
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
|
+
|
|
10
28
|
def initialize
|
|
11
|
-
@
|
|
29
|
+
@scanner = nil
|
|
30
|
+
@length = nil
|
|
12
31
|
end
|
|
13
32
|
|
|
14
33
|
def scan_setup(str)
|
|
15
|
-
@
|
|
34
|
+
@scanner = Scanner.new(str)
|
|
16
35
|
end
|
|
17
36
|
|
|
18
|
-
def
|
|
19
|
-
@
|
|
20
|
-
end
|
|
37
|
+
def next_token
|
|
38
|
+
return if @scanner.eos?
|
|
21
39
|
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
until token = scan || @scanner.eos?; end
|
|
41
|
+
token
|
|
24
42
|
end
|
|
25
43
|
|
|
26
|
-
def
|
|
27
|
-
@
|
|
44
|
+
def last_string
|
|
45
|
+
-@scanner.string.byteslice(@scanner.pos - @length, @length)
|
|
28
46
|
end
|
|
29
47
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
token
|
|
48
|
+
def last_literal
|
|
49
|
+
last_str = @scanner.string.byteslice(@scanner.pos - @length, @length)
|
|
50
|
+
last_str.tr! "\\", ""
|
|
51
|
+
-last_str
|
|
35
52
|
end
|
|
36
53
|
|
|
37
54
|
private
|
|
38
|
-
# takes advantage of String @- deduping capabilities in Ruby 2.5 upwards see:
|
|
39
|
-
# https://bugs.ruby-lang.org/issues/13077
|
|
40
|
-
def dedup_scan(regex)
|
|
41
|
-
r = @ss.scan(regex)
|
|
42
|
-
r ? -r : nil
|
|
43
|
-
end
|
|
44
|
-
|
|
45
55
|
def scan
|
|
56
|
+
next_byte = @scanner.peek_byte
|
|
46
57
|
case
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[:OR, "|"]
|
|
56
|
-
when @ss.skip(/\./)
|
|
57
|
-
[:DOT, "."]
|
|
58
|
-
when text = dedup_scan(/:\w+/)
|
|
59
|
-
[:SYMBOL, text]
|
|
60
|
-
when text = dedup_scan(/\*\w+/)
|
|
61
|
-
[:STAR, text]
|
|
62
|
-
when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
|
|
63
|
-
text.tr! "\\", ""
|
|
64
|
-
[:LITERAL, -text]
|
|
65
|
-
# any char
|
|
66
|
-
when text = dedup_scan(/./)
|
|
67
|
-
[: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
|
|
68
66
|
end
|
|
69
67
|
end
|
|
68
|
+
|
|
69
|
+
def next_byte_is_not_a_token?
|
|
70
|
+
!STATIC_TOKENS[@scanner.string.getbyte(@scanner.pos + 1)]
|
|
71
|
+
end
|
|
70
72
|
end
|
|
71
73
|
end
|
|
72
74
|
end
|
|
@@ -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
|
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# :markup: markdown
|
|
4
|
-
|
|
5
3
|
module ActionDispatch
|
|
6
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
|
5
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
|
6
|
+
|
|
7
7
|
def redirect(event)
|
|
8
8
|
payload = event.payload
|
|
9
9
|
|
|
10
10
|
info { "Redirected to #{payload[:location]}" }
|
|
11
11
|
|
|
12
|
+
if ActionDispatch.verbose_redirect_logs
|
|
13
|
+
info { "↳ #{payload[:source_location]}" }
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
info do
|
|
13
17
|
status = payload[:status]
|
|
14
18
|
|
|
@@ -116,13 +116,15 @@ module ActionDispatch
|
|
|
116
116
|
# cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
|
|
117
117
|
#
|
|
118
118
|
# # Sets a signed cookie, which prevents users from tampering with its value.
|
|
119
|
-
# # It can be read using the signed method `cookies.signed[:name]`
|
|
120
119
|
# cookies.signed[:user_id] = current_user.id
|
|
120
|
+
# # It can be read using the signed method.
|
|
121
|
+
# cookies.signed[:user_id] # => 123
|
|
121
122
|
#
|
|
122
123
|
# # Sets an encrypted cookie value before sending it to the client which
|
|
123
124
|
# # prevent users from reading and tampering with its value.
|
|
124
|
-
# # It can be read using the encrypted method `cookies.encrypted[:name]`
|
|
125
125
|
# cookies.encrypted[:discount] = 45
|
|
126
|
+
# # It can be read using the encrypted method.
|
|
127
|
+
# cookies.encrypted[:discount] # => 45
|
|
126
128
|
#
|
|
127
129
|
# # Sets a "permanent" cookie (which expires in 20 years from now).
|
|
128
130
|
# cookies.permanent[:login] = "XJ-122"
|
|
@@ -608,8 +610,10 @@ module ActionDispatch
|
|
|
608
610
|
end
|
|
609
611
|
|
|
610
612
|
def check_for_overflow!(name, options)
|
|
611
|
-
|
|
612
|
-
|
|
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"
|
|
613
617
|
end
|
|
614
618
|
end
|
|
615
619
|
end
|
|
@@ -65,7 +65,9 @@ module ActionDispatch
|
|
|
65
65
|
content_type = Mime[:text]
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
if
|
|
68
|
+
if request.head?
|
|
69
|
+
render(wrapper.status_code, "", content_type)
|
|
70
|
+
elsif api_request?(content_type)
|
|
69
71
|
render_for_api_request(content_type, wrapper)
|
|
70
72
|
else
|
|
71
73
|
render_for_browser_request(request, wrapper)
|
|
@@ -125,6 +127,7 @@ module ActionDispatch
|
|
|
125
127
|
trace_to_show: wrapper.trace_to_show,
|
|
126
128
|
routes_inspector: routes_inspector(wrapper),
|
|
127
129
|
source_extracts: wrapper.source_extracts,
|
|
130
|
+
exception_message_for_copy: compose_exception_message(wrapper).join("\n"),
|
|
128
131
|
)
|
|
129
132
|
end
|
|
130
133
|
|
|
@@ -138,22 +141,40 @@ module ActionDispatch
|
|
|
138
141
|
return unless logger
|
|
139
142
|
return if !log_rescued_responses?(request) && wrapper.rescue_response?
|
|
140
143
|
|
|
144
|
+
message = compose_exception_message(wrapper)
|
|
145
|
+
log_array(logger, message, request)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def compose_exception_message(wrapper)
|
|
141
149
|
trace = wrapper.exception_trace
|
|
142
150
|
|
|
143
151
|
message = []
|
|
144
152
|
message << " "
|
|
145
|
-
message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
|
|
146
153
|
if wrapper.has_cause?
|
|
147
|
-
message << "
|
|
154
|
+
message << "#{wrapper.exception_class_name} (#{wrapper.message})"
|
|
148
155
|
wrapper.wrapped_causes.each do |wrapped_cause|
|
|
149
|
-
message << "#{wrapped_cause.exception_class_name} (#{wrapped_cause.message})"
|
|
156
|
+
message << "Caused by: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message})"
|
|
150
157
|
end
|
|
158
|
+
|
|
159
|
+
message << "\nInformation for: #{wrapper.exception_class_name} (#{wrapper.message}):"
|
|
160
|
+
else
|
|
161
|
+
message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
|
|
151
162
|
end
|
|
163
|
+
|
|
152
164
|
message.concat(wrapper.annotated_source_code)
|
|
153
165
|
message << " "
|
|
154
166
|
message.concat(trace)
|
|
155
167
|
|
|
156
|
-
|
|
168
|
+
if wrapper.has_cause?
|
|
169
|
+
wrapper.wrapped_causes.each do |wrapped_cause|
|
|
170
|
+
message << "\nInformation for cause: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message}):"
|
|
171
|
+
message.concat(wrapped_cause.annotated_source_code)
|
|
172
|
+
message << " "
|
|
173
|
+
message.concat(wrapped_cause.exception_trace)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
message
|
|
157
178
|
end
|
|
158
179
|
|
|
159
180
|
def log_array(logger, lines, request)
|
|
@@ -15,17 +15,12 @@ module ActionDispatch
|
|
|
15
15
|
paths = RESCUES_TEMPLATE_PATHS.dup
|
|
16
16
|
lookup_context = ActionView::LookupContext.new(paths)
|
|
17
17
|
super(lookup_context, assigns, nil)
|
|
18
|
-
@exception_wrapper = assigns[:exception_wrapper]
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def compiled_method_container
|
|
22
21
|
self.class
|
|
23
22
|
end
|
|
24
23
|
|
|
25
|
-
def error_highlight_available?
|
|
26
|
-
@exception_wrapper.error_highlight_available?
|
|
27
|
-
end
|
|
28
|
-
|
|
29
24
|
def debug_params(params)
|
|
30
25
|
clean_params = params.clone
|
|
31
26
|
clean_params.delete("action")
|
|
@@ -60,6 +55,17 @@ module ActionDispatch
|
|
|
60
55
|
end
|
|
61
56
|
end
|
|
62
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
|
+
|
|
63
69
|
def protect_against_forgery?
|
|
64
70
|
false
|
|
65
71
|
end
|
|
@@ -18,11 +18,12 @@ module ActionDispatch
|
|
|
18
18
|
"ActionController::UnknownFormat" => :not_acceptable,
|
|
19
19
|
"ActionDispatch::Http::MimeNegotiation::InvalidType" => :not_acceptable,
|
|
20
20
|
"ActionController::MissingExactTemplate" => :not_acceptable,
|
|
21
|
-
"ActionController::InvalidAuthenticityToken" =>
|
|
22
|
-
"ActionController::InvalidCrossOriginRequest" =>
|
|
21
|
+
"ActionController::InvalidAuthenticityToken" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT,
|
|
22
|
+
"ActionController::InvalidCrossOriginRequest" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT,
|
|
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
|
|
@@ -173,7 +179,7 @@ module ActionDispatch
|
|
|
173
179
|
end
|
|
174
180
|
|
|
175
181
|
def self.status_code_for_exception(class_name)
|
|
176
|
-
|
|
182
|
+
ActionDispatch::Response.rack_status_code(@@rescue_responses[class_name])
|
|
177
183
|
end
|
|
178
184
|
|
|
179
185
|
def show?(request)
|
|
@@ -197,16 +203,10 @@ 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
|
|
|
204
|
-
def error_highlight_available?
|
|
205
|
-
# ErrorHighlight.spot with backtrace_location keyword is available since
|
|
206
|
-
# error_highlight 0.4.0
|
|
207
|
-
defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0")
|
|
208
|
-
end
|
|
209
|
-
|
|
210
210
|
def trace_to_show
|
|
211
211
|
if traces["Application Trace"].empty? && rescue_template != "routing_error"
|
|
212
212
|
"Full Trace"
|
|
@@ -267,13 +267,13 @@ module ActionDispatch
|
|
|
267
267
|
end
|
|
268
268
|
|
|
269
269
|
(@exception.backtrace_locations || []).map do |loc|
|
|
270
|
-
if built_methods.key?(loc.
|
|
270
|
+
if built_methods.key?(loc.base_label)
|
|
271
271
|
thread_backtrace_location = if loc.respond_to?(:__getobj__)
|
|
272
272
|
loc.__getobj__
|
|
273
273
|
else
|
|
274
274
|
loc
|
|
275
275
|
end
|
|
276
|
-
SourceMapLocation.new(thread_backtrace_location, built_methods[loc.
|
|
276
|
+
SourceMapLocation.new(thread_backtrace_location, built_methods[loc.base_label])
|
|
277
277
|
else
|
|
278
278
|
loc
|
|
279
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,12 +24,21 @@ module ActionDispatch
|
|
|
20
24
|
@executor.error_reporter.report(error, handled: false, source: "application.action_dispatch")
|
|
21
25
|
end
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
unless response_finished
|
|
28
|
+
response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
|
|
29
|
+
end
|
|
30
|
+
returned = true
|
|
31
|
+
response
|
|
32
|
+
rescue Exception => error
|
|
33
|
+
request = ActionDispatch::Request.new env
|
|
34
|
+
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
|
35
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, error)
|
|
36
|
+
@executor.error_reporter.report(wrapper.unwrapped_exception, handled: false, source: "application.action_dispatch")
|
|
26
37
|
raise
|
|
27
38
|
ensure
|
|
28
|
-
|
|
39
|
+
if !returned && !response_finished
|
|
40
|
+
state.complete!
|
|
41
|
+
end
|
|
29
42
|
end
|
|
30
43
|
end
|
|
31
44
|
end
|
|
@@ -25,14 +25,14 @@ 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"
|
|
32
|
+
render_format(status, content_type, "")
|
|
33
|
+
else
|
|
34
|
+
render(status, content_type, body)
|
|
35
|
+
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
private
|
|
@@ -44,6 +44,8 @@ module ActionDispatch
|
|
|
44
44
|
"10.0.0.0/8", # private IPv4 range 10.x.x.x
|
|
45
45
|
"172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
|
46
46
|
"192.168.0.0/16", # private IPv4 range 192.168.x.x
|
|
47
|
+
"169.254.0.0/16", # link-local IPv4 range 169.254.x.x
|
|
48
|
+
"fe80::/10", # link-local IPv6 range fe80::/10
|
|
47
49
|
].map { |proxy| IPAddr.new(proxy) }
|
|
48
50
|
|
|
49
51
|
attr_reader :check_ip, :proxies
|
|
@@ -126,11 +128,11 @@ module ActionDispatch
|
|
|
126
128
|
# left, which was presumably set by one of those proxies.
|
|
127
129
|
def calculate_ip
|
|
128
130
|
# Set by the Rack web server, this is a single value.
|
|
129
|
-
remote_addr = ips_from(@req.remote_addr).last
|
|
131
|
+
remote_addr = sanitize_ips(ips_from(@req.remote_addr)).last
|
|
130
132
|
|
|
131
133
|
# Could be a CSV list and/or repeated headers that were concatenated.
|
|
132
|
-
client_ips = ips_from(@req.client_ip).reverse!
|
|
133
|
-
forwarded_ips =
|
|
134
|
+
client_ips = sanitize_ips(ips_from(@req.client_ip)).reverse!
|
|
135
|
+
forwarded_ips = sanitize_ips(@req.forwarded_for || []).reverse!
|
|
134
136
|
|
|
135
137
|
# `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
|
|
136
138
|
# are both set, it means that either:
|
|
@@ -150,7 +152,8 @@ module ActionDispatch
|
|
|
150
152
|
# We don't know which came from the proxy, and which from the user
|
|
151
153
|
raise IpSpoofAttackError, "IP spoofing attack?! " \
|
|
152
154
|
"HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
|
|
153
|
-
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
|
|
155
|
+
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}" \
|
|
156
|
+
" HTTP_FORWARDED=" + @req.forwarded_for.map { "for=#{_1}" }.join(", ").inspect if @req.forwarded_for.any?
|
|
154
157
|
end
|
|
155
158
|
|
|
156
159
|
# We assume these things about the IP headers:
|
|
@@ -176,7 +179,10 @@ module ActionDispatch
|
|
|
176
179
|
def ips_from(header) # :doc:
|
|
177
180
|
return [] unless header
|
|
178
181
|
# Split the comma-separated list into an array of strings.
|
|
179
|
-
|
|
182
|
+
header.strip.split(/[,\s]+/)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def sanitize_ips(ips) # :doc:
|
|
180
186
|
ips.select! do |ip|
|
|
181
187
|
# Only return IPs that are valid according to the IPAddr#new method.
|
|
182
188
|
range = IPAddr.new(ip).to_range
|
|
@@ -25,11 +25,12 @@ module ActionDispatch
|
|
|
25
25
|
def initialize(app, header:)
|
|
26
26
|
@app = app
|
|
27
27
|
@header = header
|
|
28
|
+
@env_header = "HTTP_#{header.upcase.tr("-", "_")}"
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def call(env)
|
|
31
32
|
req = ActionDispatch::Request.new env
|
|
32
|
-
req.request_id = make_request_id(req.
|
|
33
|
+
req.request_id = make_request_id(req.get_header(@env_header))
|
|
33
34
|
@app.call(env).tap { |_status, headers, _body| headers[@header] = req.request_id }
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -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
|
|
@@ -11,9 +11,11 @@ module ActionDispatch
|
|
|
11
11
|
#
|
|
12
12
|
# 1. **TLS redirect**: Permanently redirects `http://` requests to `https://`
|
|
13
13
|
# with the same URL host, path, etc. Enabled by default. Set
|
|
14
|
-
# `config.ssl_options` to modify the destination URL
|
|
15
|
-
#
|
|
16
|
-
#
|
|
14
|
+
# `config.ssl_options` to modify the destination URL:
|
|
15
|
+
#
|
|
16
|
+
# config.ssl_options = { redirect: { host: "secure.widgets.com", port: 8080 }`
|
|
17
|
+
#
|
|
18
|
+
# Or set `redirect: false` to disable redirection.
|
|
17
19
|
#
|
|
18
20
|
# Requests can opt-out of redirection with `exclude`:
|
|
19
21
|
#
|
|
@@ -21,6 +23,14 @@ module ActionDispatch
|
|
|
21
23
|
#
|
|
22
24
|
# Cookies will not be flagged as secure for excluded requests.
|
|
23
25
|
#
|
|
26
|
+
# When proxying through a load balancer that terminates SSL, the forwarded
|
|
27
|
+
# request will appear as though it's HTTP instead of HTTPS to the application.
|
|
28
|
+
# This makes redirects and cookie security target HTTP instead of HTTPS.
|
|
29
|
+
# To make the server assume that the proxy already terminated SSL, and
|
|
30
|
+
# that the request really is HTTPS, set `config.assume_ssl` to `true`:
|
|
31
|
+
#
|
|
32
|
+
# config.assume_ssl = true
|
|
33
|
+
#
|
|
24
34
|
# 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers
|
|
25
35
|
# they must not be sent along with `http://` requests. Enabled by default.
|
|
26
36
|
# Set `config.ssl_options` with `secure_cookies: false` to disable this
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<button onclick="copyAsText.bind(this)()">Copy as text</button>
|