actionpack 6.1.7.3 → 7.0.8
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +320 -390
- data/MIT-LICENSE +1 -0
- data/README.rdoc +4 -5
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +13 -26
- data/lib/abstract_controller/caching/fragments.rb +2 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +21 -7
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +17 -12
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +9 -11
- data/lib/abstract_controller/translation.rb +5 -4
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/action_controller/api.rb +7 -7
- data/lib/action_controller/base.rb +5 -4
- data/lib/action_controller/form_builder.rb +2 -2
- data/lib/action_controller/log_subscriber.rb +4 -3
- data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +137 -102
- data/lib/action_controller/metal/content_security_policy.rb +36 -2
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +23 -31
- data/lib/action_controller/metal/etag_with_flash.rb +1 -1
- data/lib/action_controller/metal/exceptions.rb +19 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- data/lib/action_controller/metal/head.rb +1 -1
- data/lib/action_controller/metal/helpers.rb +2 -2
- data/lib/action_controller/metal/http_authentication.rb +66 -39
- data/lib/action_controller/metal/instrumentation.rb +57 -52
- data/lib/action_controller/metal/live.rb +43 -2
- data/lib/action_controller/metal/mime_responds.rb +3 -3
- data/lib/action_controller/metal/params_wrapper.rb +20 -11
- data/lib/action_controller/metal/permissions_policy.rb +19 -28
- data/lib/action_controller/metal/redirecting.rb +111 -19
- data/lib/action_controller/metal/renderers.rb +12 -13
- data/lib/action_controller/metal/rendering.rb +121 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +83 -32
- data/lib/action_controller/metal/rescue.rb +5 -4
- data/lib/action_controller/metal/streaming.rb +7 -9
- data/lib/action_controller/metal/strong_parameters.rb +138 -115
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +3 -5
- data/lib/action_controller/metal.rb +10 -13
- data/lib/action_controller/railtie.rb +50 -6
- data/lib/action_controller/renderer.rb +1 -20
- data/lib/action_controller/test_case.rb +28 -7
- data/lib/action_controller.rb +2 -5
- data/lib/action_dispatch/http/cache.rb +20 -13
- data/lib/action_dispatch/http/content_security_policy.rb +113 -36
- data/lib/action_dispatch/http/filter_parameters.rb +4 -19
- data/lib/action_dispatch/http/headers.rb +1 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +15 -5
- data/lib/action_dispatch/http/mime_type.rb +9 -11
- data/lib/action_dispatch/http/parameters.rb +5 -5
- data/lib/action_dispatch/http/permissions_policy.rb +17 -1
- data/lib/action_dispatch/http/request.rb +27 -37
- data/lib/action_dispatch/http/response.rb +3 -20
- data/lib/action_dispatch/http/upload.rb +13 -2
- data/lib/action_dispatch/http/url.rb +11 -19
- data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
- data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
- data/lib/action_dispatch/journey/nodes/node.rb +70 -5
- data/lib/action_dispatch/journey/path/pattern.rb +22 -13
- data/lib/action_dispatch/journey/route.rb +6 -13
- data/lib/action_dispatch/journey/router/utils.rb +2 -2
- data/lib/action_dispatch/journey/router.rb +1 -1
- data/lib/action_dispatch/journey/routes.rb +3 -3
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
- data/lib/action_dispatch/middleware/cookies.rb +20 -13
- data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
- data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
- data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
- data/lib/action_dispatch/middleware/executor.rb +3 -0
- data/lib/action_dispatch/middleware/flash.rb +17 -18
- data/lib/action_dispatch/middleware/host_authorization.rb +13 -17
- data/lib/action_dispatch/middleware/remote_ip.rb +20 -8
- data/lib/action_dispatch/middleware/request_id.rb +3 -3
- data/lib/action_dispatch/middleware/server_timing.rb +76 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
- data/lib/action_dispatch/middleware/session/cookie_store.rb +9 -9
- data/lib/action_dispatch/middleware/show_exceptions.rb +17 -16
- data/lib/action_dispatch/middleware/stack.rb +27 -9
- data/lib/action_dispatch/middleware/static.rb +5 -9
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +10 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -3
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +22 -22
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +43 -13
- data/lib/action_dispatch/routing/inspector.rb +1 -1
- data/lib/action_dispatch/routing/mapper.rb +82 -83
- data/lib/action_dispatch/routing/redirection.rb +5 -2
- data/lib/action_dispatch/routing/route_set.rb +17 -7
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +24 -25
- data/lib/action_dispatch/routing.rb +5 -6
- data/lib/action_dispatch/system_test_case.rb +5 -5
- data/lib/action_dispatch/system_testing/browser.rb +3 -13
- data/lib/action_dispatch/system_testing/driver.rb +34 -10
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +11 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +0 -8
- data/lib/action_dispatch/testing/assertions/response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/routing.rb +3 -2
- data/lib/action_dispatch/testing/assertions.rb +2 -5
- data/lib/action_dispatch/testing/integration.rb +6 -8
- data/lib/action_dispatch/testing/test_process.rb +3 -29
- data/lib/action_dispatch/testing/test_response.rb +20 -2
- data/lib/action_dispatch.rb +1 -0
- data/lib/action_pack/gem_version.rb +5 -5
- data/lib/action_pack/version.rb +1 -1
- metadata +16 -15
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "action_dispatch/http/request"
|
4
3
|
require "action_dispatch/middleware/exception_wrapper"
|
5
4
|
require "action_dispatch/routing/inspector"
|
6
5
|
|
@@ -135,6 +134,7 @@ module ActionDispatch
|
|
135
134
|
logger = logger(request)
|
136
135
|
|
137
136
|
return unless logger
|
137
|
+
return if !log_rescued_responses?(request) && wrapper.rescue_response?
|
138
138
|
|
139
139
|
exception = wrapper.exception
|
140
140
|
trace = wrapper.exception_trace
|
@@ -149,9 +149,7 @@ module ActionDispatch
|
|
149
149
|
log_array(logger, message)
|
150
150
|
end
|
151
151
|
|
152
|
-
def log_array(logger,
|
153
|
-
lines = Array(array)
|
154
|
-
|
152
|
+
def log_array(logger, lines)
|
155
153
|
return if lines.empty?
|
156
154
|
|
157
155
|
if logger.formatter && logger.formatter.respond_to?(:tags_text)
|
@@ -178,5 +176,9 @@ module ActionDispatch
|
|
178
176
|
def api_request?(content_type)
|
179
177
|
@response_format == :api && !content_type.html?
|
180
178
|
end
|
179
|
+
|
180
|
+
def log_rescued_responses?(request)
|
181
|
+
request.get_header("action_dispatch.log_rescued_responses")
|
182
|
+
end
|
181
183
|
end
|
182
184
|
end
|
@@ -9,9 +9,9 @@ module ActionDispatch
|
|
9
9
|
# config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
|
10
10
|
#
|
11
11
|
# After restarting the application and re-triggering the deadlock condition,
|
12
|
-
# <tt>/rails/locks</tt> will show a summary of all threads currently
|
13
|
-
# the interlock, which lock level they are holding or awaiting, and
|
14
|
-
# current backtrace.
|
12
|
+
# the route <tt>/rails/locks</tt> will show a summary of all threads currently
|
13
|
+
# known to the interlock, which lock level they are holding or awaiting, and
|
14
|
+
# their current backtrace.
|
15
15
|
#
|
16
16
|
# Generally a deadlock will be caused by the interlock conflicting with some
|
17
17
|
# other external lock or blocking I/O call. These cannot be automatically
|
@@ -118,6 +118,10 @@ module ActionDispatch
|
|
118
118
|
Rack::Utils.status_code(@@rescue_responses[class_name])
|
119
119
|
end
|
120
120
|
|
121
|
+
def rescue_response?
|
122
|
+
@@rescue_responses.key?(exception.class.name)
|
123
|
+
end
|
124
|
+
|
121
125
|
def source_extracts
|
122
126
|
backtrace.map do |trace|
|
123
127
|
file, line_number = extract_file_and_line_number(trace)
|
@@ -13,6 +13,9 @@ module ActionDispatch
|
|
13
13
|
begin
|
14
14
|
response = @app.call(env)
|
15
15
|
returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
|
16
|
+
rescue => error
|
17
|
+
@executor.error_reporter.report(error, handled: false)
|
18
|
+
raise
|
16
19
|
ensure
|
17
20
|
state.complete! unless returned
|
18
21
|
end
|
@@ -20,10 +20,11 @@ module ActionDispatch
|
|
20
20
|
# end
|
21
21
|
# end
|
22
22
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
23
|
+
# Then in +show.html.erb+:
|
24
|
+
#
|
25
|
+
# <% if flash[:notice] %>
|
26
|
+
# <div class="notice"><%= flash[:notice] %></div>
|
27
|
+
# <% end %>
|
27
28
|
#
|
28
29
|
# Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
|
29
30
|
#
|
@@ -41,9 +42,9 @@ module ActionDispatch
|
|
41
42
|
KEY = "action_dispatch.request.flash_hash"
|
42
43
|
|
43
44
|
module RequestMethods
|
44
|
-
# Access the contents of the flash.
|
45
|
-
#
|
46
|
-
#
|
45
|
+
# Access the contents of the flash. Returns a ActionDispatch::Flash::FlashHash.
|
46
|
+
#
|
47
|
+
# See ActionDispatch::Flash for example usage.
|
47
48
|
def flash
|
48
49
|
flash = flash_hash
|
49
50
|
return flash if flash
|
@@ -59,16 +60,14 @@ module ActionDispatch
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def commit_flash # :nodoc:
|
62
|
-
session
|
63
|
-
flash_hash = self.flash_hash
|
63
|
+
return unless session.enabled?
|
64
64
|
|
65
65
|
if flash_hash && (flash_hash.present? || session.key?("flash"))
|
66
66
|
session["flash"] = flash_hash.to_session_value
|
67
67
|
self.flash = flash_hash.dup
|
68
68
|
end
|
69
69
|
|
70
|
-
if
|
71
|
-
session.key?("flash") && session["flash"].nil?
|
70
|
+
if session.loaded? && session.key?("flash") && session["flash"].nil?
|
72
71
|
session.delete("flash")
|
73
72
|
end
|
74
73
|
end
|
@@ -79,7 +78,7 @@ module ActionDispatch
|
|
79
78
|
end
|
80
79
|
end
|
81
80
|
|
82
|
-
class FlashNow
|
81
|
+
class FlashNow # :nodoc:
|
83
82
|
attr_accessor :flash
|
84
83
|
|
85
84
|
def initialize(flash)
|
@@ -111,7 +110,7 @@ module ActionDispatch
|
|
111
110
|
class FlashHash
|
112
111
|
include Enumerable
|
113
112
|
|
114
|
-
def self.from_session_value(value)
|
113
|
+
def self.from_session_value(value) # :nodoc:
|
115
114
|
case value
|
116
115
|
when FlashHash # Rails 3.1, 3.2
|
117
116
|
flashes = value.instance_variable_get(:@flashes)
|
@@ -132,13 +131,13 @@ module ActionDispatch
|
|
132
131
|
|
133
132
|
# Builds a hash containing the flashes to keep for the next request.
|
134
133
|
# If there are none to keep, returns +nil+.
|
135
|
-
def to_session_value
|
134
|
+
def to_session_value # :nodoc:
|
136
135
|
flashes_to_keep = @flashes.except(*@discard)
|
137
136
|
return nil if flashes_to_keep.empty?
|
138
137
|
{ "discard" => [], "flashes" => flashes_to_keep }
|
139
138
|
end
|
140
139
|
|
141
|
-
def initialize(flashes = {}, discard = [])
|
140
|
+
def initialize(flashes = {}, discard = []) # :nodoc:
|
142
141
|
@discard = Set.new(stringify_array(discard))
|
143
142
|
@flashes = flashes.stringify_keys
|
144
143
|
@now = nil
|
@@ -162,7 +161,7 @@ module ActionDispatch
|
|
162
161
|
@flashes[k.to_s]
|
163
162
|
end
|
164
163
|
|
165
|
-
def update(h)
|
164
|
+
def update(h) # :nodoc:
|
166
165
|
@discard.subtract stringify_array(h.keys)
|
167
166
|
@flashes.update h.stringify_keys
|
168
167
|
self
|
@@ -202,7 +201,7 @@ module ActionDispatch
|
|
202
201
|
|
203
202
|
alias :merge! :update
|
204
203
|
|
205
|
-
def replace(h)
|
204
|
+
def replace(h) # :nodoc:
|
206
205
|
@discard.clear
|
207
206
|
@flashes.replace h.stringify_keys
|
208
207
|
self
|
@@ -253,7 +252,7 @@ module ActionDispatch
|
|
253
252
|
# Mark for removal entries that were kept, and delete unkept ones.
|
254
253
|
#
|
255
254
|
# This method is called automatically by filters, so you generally don't need to care about it.
|
256
|
-
def sweep
|
255
|
+
def sweep # :nodoc:
|
257
256
|
@discard.each { |k| @flashes.delete k }
|
258
257
|
@discard.replace @flashes.keys
|
259
258
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "action_dispatch/http/request"
|
4
|
-
|
5
3
|
module ActionDispatch
|
6
4
|
# This middleware guards from DNS rebinding attacks by explicitly permitting
|
7
5
|
# the hosts a request can be sent to, and is passed the options set in
|
@@ -97,7 +95,7 @@ module ActionDispatch
|
|
97
95
|
def response_body(request)
|
98
96
|
return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
|
99
97
|
|
100
|
-
template = DebugView.new(
|
98
|
+
template = DebugView.new(hosts: request.env["action_dispatch.blocked_hosts"])
|
101
99
|
template.render(template: "rescues/blocked_host", layout: "rescues/layout")
|
102
100
|
end
|
103
101
|
|
@@ -113,7 +111,7 @@ module ActionDispatch
|
|
113
111
|
|
114
112
|
return unless logger
|
115
113
|
|
116
|
-
logger.error("[#{self.class.name}] Blocked
|
114
|
+
logger.error("[#{self.class.name}] Blocked hosts: #{request.env["action_dispatch.blocked_hosts"].join(", ")}")
|
117
115
|
end
|
118
116
|
|
119
117
|
def available_logger(request)
|
@@ -121,20 +119,11 @@ module ActionDispatch
|
|
121
119
|
end
|
122
120
|
end
|
123
121
|
|
124
|
-
def initialize(app, hosts,
|
122
|
+
def initialize(app, hosts, exclude: nil, response_app: nil)
|
125
123
|
@app = app
|
126
124
|
@permissions = Permissions.new(hosts)
|
127
125
|
@exclude = exclude
|
128
126
|
|
129
|
-
unless deprecated_response_app.nil?
|
130
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
131
|
-
`action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 7.0.
|
132
|
-
Use the Host Authorization `response_app` setting instead.
|
133
|
-
MSG
|
134
|
-
|
135
|
-
response_app ||= deprecated_response_app
|
136
|
-
end
|
137
|
-
|
138
127
|
@response_app = response_app || DefaultResponseApp.new
|
139
128
|
end
|
140
129
|
|
@@ -142,21 +131,28 @@ module ActionDispatch
|
|
142
131
|
return @app.call(env) if @permissions.empty?
|
143
132
|
|
144
133
|
request = Request.new(env)
|
134
|
+
hosts = blocked_hosts(request)
|
145
135
|
|
146
|
-
if
|
136
|
+
if hosts.empty? || excluded?(request)
|
147
137
|
mark_as_authorized(request)
|
148
138
|
@app.call(env)
|
149
139
|
else
|
140
|
+
env["action_dispatch.blocked_hosts"] = hosts
|
150
141
|
@response_app.call(env)
|
151
142
|
end
|
152
143
|
end
|
153
144
|
|
154
145
|
private
|
155
|
-
def
|
146
|
+
def blocked_hosts(request)
|
147
|
+
hosts = []
|
148
|
+
|
156
149
|
origin_host = request.get_header("HTTP_HOST")
|
150
|
+
hosts << origin_host unless @permissions.allows?(origin_host)
|
151
|
+
|
157
152
|
forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last
|
153
|
+
hosts << forwarded_host unless forwarded_host.blank? || @permissions.allows?(forwarded_host)
|
158
154
|
|
159
|
-
|
155
|
+
hosts
|
160
156
|
end
|
161
157
|
|
162
158
|
def excluded?(request)
|
@@ -22,7 +22,7 @@ module ActionDispatch
|
|
22
22
|
# This middleware assumes that there is at least one proxy sitting around
|
23
23
|
# and setting headers with the client's remote IP address. If you don't use
|
24
24
|
# a proxy, because you are hosted on e.g. Heroku without SSL, any client can
|
25
|
-
# claim to have any IP address by setting the X-Forwarded-For header. If you
|
25
|
+
# claim to have any IP address by setting the +X-Forwarded-For+ header. If you
|
26
26
|
# care about that, then you need to explicitly drop or ignore those headers
|
27
27
|
# sometime before this middleware runs.
|
28
28
|
class RemoteIp
|
@@ -51,11 +51,9 @@ module ActionDispatch
|
|
51
51
|
# clients (like WAP devices), or behind proxies that set headers in an
|
52
52
|
# incorrect or confusing way (like AWS ELB).
|
53
53
|
#
|
54
|
-
# The +custom_proxies+ argument can take an
|
55
|
-
#
|
56
|
-
#
|
57
|
-
# addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
|
58
|
-
# want in the middle (or at the beginning) of the X-Forwarded-For list,
|
54
|
+
# The +custom_proxies+ argument can take an enumerable which will be used
|
55
|
+
# instead of +TRUSTED_PROXIES+. Any proxy setup will put the value you
|
56
|
+
# want in the middle (or at the beginning) of the +X-Forwarded-For+ list,
|
59
57
|
# with your proxy servers after it. If your proxies aren't removed, pass
|
60
58
|
# them in via the +custom_proxies+ parameter. That way, the middleware will
|
61
59
|
# ignore those IP addresses, and return the one that you want.
|
@@ -67,6 +65,20 @@ module ActionDispatch
|
|
67
65
|
elsif custom_proxies.respond_to?(:any?)
|
68
66
|
custom_proxies
|
69
67
|
else
|
68
|
+
ActiveSupport::Deprecation.warn(<<~EOM)
|
69
|
+
Setting config.action_dispatch.trusted_proxies to a single value has
|
70
|
+
been deprecated. Please set this to an enumerable instead. For
|
71
|
+
example, instead of:
|
72
|
+
|
73
|
+
config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
|
74
|
+
|
75
|
+
Wrap the value in an Array:
|
76
|
+
|
77
|
+
config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
|
78
|
+
|
79
|
+
Note that unlike passing a single argument, passing an enumerable
|
80
|
+
will *replace* the default set of trusted proxies.
|
81
|
+
EOM
|
70
82
|
Array(custom_proxies) + TRUSTED_PROXIES
|
71
83
|
end
|
72
84
|
end
|
@@ -98,9 +110,9 @@ module ActionDispatch
|
|
98
110
|
# REMOTE_ADDR will be correct if the request is made directly against the
|
99
111
|
# Ruby process, on e.g. Heroku. When the request is proxied by another
|
100
112
|
# server like HAProxy or NGINX, the IP address that made the original
|
101
|
-
# request will be put in an X-Forwarded-For header. If there are multiple
|
113
|
+
# request will be put in an +X-Forwarded-For+ header. If there are multiple
|
102
114
|
# proxies, that header may contain a list of IPs. Other proxy services
|
103
|
-
# set the Client-Ip header instead, so we check that too.
|
115
|
+
# set the +Client-Ip+ header instead, so we check that too.
|
104
116
|
#
|
105
117
|
# As discussed in {this post about Rails IP Spoofing}[https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
|
106
118
|
# while the first IP in the list is likely to be the "originating" IP,
|
@@ -5,10 +5,10 @@ require "active_support/core_ext/string/access"
|
|
5
5
|
|
6
6
|
module ActionDispatch
|
7
7
|
# Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
|
8
|
-
# through
|
9
|
-
# the same id to the client via the X-Request-Id header.
|
8
|
+
# through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends
|
9
|
+
# the same id to the client via the +X-Request-Id+ header.
|
10
10
|
#
|
11
|
-
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
|
11
|
+
# The unique request id is either based on the +X-Request-Id+ header in the request, which would typically be generated
|
12
12
|
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
|
13
13
|
# header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
|
14
14
|
#
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/notifications"
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
class ServerTiming
|
7
|
+
SERVER_TIMING_HEADER = "Server-Timing"
|
8
|
+
|
9
|
+
class Subscriber # :nodoc:
|
10
|
+
include Singleton
|
11
|
+
KEY = :action_dispatch_server_timing_events
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@mutex = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(event)
|
18
|
+
if events = ActiveSupport::IsolatedExecutionState[KEY]
|
19
|
+
events << event
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def collect_events
|
24
|
+
events = []
|
25
|
+
ActiveSupport::IsolatedExecutionState[KEY] = events
|
26
|
+
yield
|
27
|
+
events
|
28
|
+
ensure
|
29
|
+
ActiveSupport::IsolatedExecutionState.delete(KEY)
|
30
|
+
end
|
31
|
+
|
32
|
+
def ensure_subscribed
|
33
|
+
@mutex.synchronize do
|
34
|
+
# Subscribe to all events, except those beginning with "!"
|
35
|
+
# Ideally we would be more selective of what is being measured
|
36
|
+
@subscriber ||= ActiveSupport::Notifications.subscribe(/\A[^!]/, self)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def unsubscribe
|
41
|
+
@mutex.synchronize do
|
42
|
+
ActiveSupport::Notifications.unsubscribe @subscriber
|
43
|
+
@subscriber = nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.unsubscribe # :nodoc:
|
49
|
+
Subscriber.instance.unsubscribe
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(app)
|
53
|
+
@app = app
|
54
|
+
@subscriber = Subscriber.instance
|
55
|
+
@subscriber.ensure_subscribed
|
56
|
+
end
|
57
|
+
|
58
|
+
def call(env)
|
59
|
+
response = nil
|
60
|
+
events = @subscriber.collect_events do
|
61
|
+
response = @app.call(env)
|
62
|
+
end
|
63
|
+
|
64
|
+
headers = response[1]
|
65
|
+
|
66
|
+
header_info = events.group_by(&:name).map do |event_name, events_collection|
|
67
|
+
"%s;dur=%.2f" % [event_name, events_collection.sum(&:duration)]
|
68
|
+
end
|
69
|
+
|
70
|
+
header_info.prepend(headers[SERVER_TIMING_HEADER]) if headers[SERVER_TIMING_HEADER].present?
|
71
|
+
headers[SERVER_TIMING_HEADER] = header_info.join(", ")
|
72
|
+
|
73
|
+
response
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -8,7 +8,7 @@ require "action_dispatch/request/session"
|
|
8
8
|
|
9
9
|
module ActionDispatch
|
10
10
|
module Session
|
11
|
-
class SessionRestoreError < StandardError
|
11
|
+
class SessionRestoreError < StandardError # :nodoc:
|
12
12
|
def initialize
|
13
13
|
super("Session contains objects whose class definition isn't available.\n" \
|
14
14
|
"Remember to require the classes for all objects kept in the session.\n" \
|
@@ -9,14 +9,14 @@ module ActionDispatch
|
|
9
9
|
# This cookie-based session store is the Rails default. It is
|
10
10
|
# dramatically faster than the alternatives.
|
11
11
|
#
|
12
|
-
# Sessions typically contain at most a
|
13
|
-
# within the 4096 bytes cookie size limit. A CookieOverflow exception is raised if
|
12
|
+
# Sessions typically contain at most a user ID and flash message; both fit
|
13
|
+
# within the 4096 bytes cookie size limit. A +CookieOverflow+ exception is raised if
|
14
14
|
# you attempt to store more than 4096 bytes of data.
|
15
15
|
#
|
16
16
|
# The cookie jar used for storage is automatically configured to be the
|
17
17
|
# best possible option given your application's configuration.
|
18
18
|
#
|
19
|
-
# Your cookies will be encrypted using your
|
19
|
+
# Your cookies will be encrypted using your application's +secret_key_base+. This
|
20
20
|
# goes a step further than signed cookies in that encrypted cookies cannot
|
21
21
|
# be altered or read by users. This is the default starting in Rails 4.
|
22
22
|
#
|
@@ -24,28 +24,28 @@ module ActionDispatch
|
|
24
24
|
#
|
25
25
|
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
|
26
26
|
#
|
27
|
-
# In the development and test environments your application's
|
27
|
+
# In the development and test environments your application's +secret_key_base+ is
|
28
28
|
# generated by Rails and stored in a temporary file in <tt>tmp/development_secret.txt</tt>.
|
29
29
|
# In all other environments, it is stored encrypted in the
|
30
30
|
# <tt>config/credentials.yml.enc</tt> file.
|
31
31
|
#
|
32
|
-
# If your application was not updated to Rails 5.2 defaults, the secret_key_base
|
32
|
+
# If your application was not updated to Rails 5.2 defaults, the +secret_key_base+
|
33
33
|
# will be found in the old <tt>config/secrets.yml</tt> file.
|
34
34
|
#
|
35
|
-
# Note that changing your secret_key_base will invalidate all existing session.
|
35
|
+
# Note that changing your +secret_key_base+ will invalidate all existing session.
|
36
36
|
# Additionally, you should take care to make sure you are not relying on the
|
37
37
|
# ability to decode signed cookies generated by your app in external
|
38
38
|
# applications or JavaScript before changing it.
|
39
39
|
#
|
40
|
-
# Because CookieStore extends Rack::Session::Abstract::Persisted
|
40
|
+
# Because CookieStore extends +Rack::Session::Abstract::Persisted+, many of the
|
41
41
|
# options described there can be used to customize the session cookie that
|
42
42
|
# is generated. For example:
|
43
43
|
#
|
44
44
|
# Rails.application.config.session_store :cookie_store, expire_after: 14.days
|
45
45
|
#
|
46
46
|
# would set the session cookie to expire automatically 14 days after creation.
|
47
|
-
# Other useful options include <tt>:key</tt>, <tt>:secure</tt
|
48
|
-
# <tt>:httponly</tt>.
|
47
|
+
# Other useful options include <tt>:key</tt>, <tt>:secure</tt>,
|
48
|
+
# <tt>:httponly</tt>, and <tt>:same_site</tt>.
|
49
49
|
class CookieStore < AbstractSecureStore
|
50
50
|
class SessionId < DelegateClass(Rack::Session::SessionId)
|
51
51
|
attr_reader :cookie_value
|
@@ -1,28 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "action_dispatch/http/request"
|
4
3
|
require "action_dispatch/middleware/exception_wrapper"
|
5
4
|
|
6
5
|
module ActionDispatch
|
7
6
|
# This middleware rescues any exception returned by the application
|
8
7
|
# and calls an exceptions app that will wrap it in a format for the end user.
|
9
8
|
#
|
10
|
-
# The exceptions app should be passed as parameter on initialization
|
11
|
-
#
|
12
|
-
# store the exception in env["action_dispatch.exception"]
|
13
|
-
# PATH_INFO to the exception status code and call the Rack app.
|
9
|
+
# The exceptions app should be passed as a parameter on initialization of
|
10
|
+
# +ShowExceptions+. Every time there is an exception, +ShowExceptions+ will
|
11
|
+
# store the exception in <tt>env["action_dispatch.exception"]</tt>, rewrite
|
12
|
+
# the +PATH_INFO+ to the exception status code and call the Rack app.
|
14
13
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
14
|
+
# In \Rails applications, the exceptions app can be configured with
|
15
|
+
# +config.exceptions_app+, which defaults to ActionDispatch::PublicExceptions.
|
16
|
+
#
|
17
|
+
# If the application returns an <tt>"X-Cascade" => "pass"</tt> response, this
|
18
|
+
# middleware will send an empty response as a result with the correct status
|
19
|
+
# code. If any exception happens inside the exceptions app, this middleware
|
20
|
+
# catches the exceptions and returns a failsafe response.
|
19
21
|
class ShowExceptions
|
20
|
-
FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" },
|
21
|
-
["500 Internal Server Error\n" \
|
22
|
-
"If you are the administrator of this website, then please read this web " \
|
23
|
-
"application's log file and/or the web server's log file to find out what " \
|
24
|
-
"went wrong."]]
|
25
|
-
|
26
22
|
def initialize(app, exceptions_app)
|
27
23
|
@app = app
|
28
24
|
@exceptions_app = exceptions_app
|
@@ -54,7 +50,12 @@ module ActionDispatch
|
|
54
50
|
response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
|
55
51
|
rescue Exception => failsafe_error
|
56
52
|
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
57
|
-
|
53
|
+
|
54
|
+
[500, { "Content-Type" => "text/plain" },
|
55
|
+
["500 Internal Server Error\n" \
|
56
|
+
"If you are the administrator of this website, then please read this web " \
|
57
|
+
"application's log file and/or the web server's log file to find out what " \
|
58
|
+
"went wrong."]]
|
58
59
|
end
|
59
60
|
|
60
61
|
def fallback_to_html_format_if_invalid_mime_type(request)
|
@@ -72,8 +72,8 @@ module ActionDispatch
|
|
72
72
|
yield(self) if block_given?
|
73
73
|
end
|
74
74
|
|
75
|
-
def each
|
76
|
-
@middlewares.each
|
75
|
+
def each(&block)
|
76
|
+
@middlewares.each(&block)
|
77
77
|
end
|
78
78
|
|
79
79
|
def size
|
@@ -91,7 +91,7 @@ module ActionDispatch
|
|
91
91
|
def unshift(klass, *args, &block)
|
92
92
|
middlewares.unshift(build_middleware(klass, args, block))
|
93
93
|
end
|
94
|
-
ruby2_keywords(:unshift)
|
94
|
+
ruby2_keywords(:unshift)
|
95
95
|
|
96
96
|
def initialize_copy(other)
|
97
97
|
self.middlewares = other.middlewares.dup
|
@@ -101,7 +101,7 @@ module ActionDispatch
|
|
101
101
|
index = assert_index(index, :before)
|
102
102
|
middlewares.insert(index, build_middleware(klass, args, block))
|
103
103
|
end
|
104
|
-
ruby2_keywords(:insert)
|
104
|
+
ruby2_keywords(:insert)
|
105
105
|
|
106
106
|
alias_method :insert_before, :insert
|
107
107
|
|
@@ -109,17 +109,29 @@ module ActionDispatch
|
|
109
109
|
index = assert_index(index, :after)
|
110
110
|
insert(index + 1, *args, &block)
|
111
111
|
end
|
112
|
-
ruby2_keywords(:insert_after)
|
112
|
+
ruby2_keywords(:insert_after)
|
113
113
|
|
114
114
|
def swap(target, *args, &block)
|
115
115
|
index = assert_index(target, :before)
|
116
116
|
insert(index, *args, &block)
|
117
117
|
middlewares.delete_at(index + 1)
|
118
118
|
end
|
119
|
-
ruby2_keywords(:swap)
|
119
|
+
ruby2_keywords(:swap)
|
120
120
|
|
121
|
+
# Deletes a middleware from the middleware stack.
|
122
|
+
#
|
123
|
+
# Returns the array of middlewares not including the deleted item, or
|
124
|
+
# returns nil if the target is not found.
|
121
125
|
def delete(target)
|
122
|
-
middlewares.
|
126
|
+
middlewares.reject! { |m| m.name == target.name }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Deletes a middleware from the middleware stack.
|
130
|
+
#
|
131
|
+
# Returns the array of middlewares not including the deleted item, or
|
132
|
+
# raises +RuntimeError+ if the target is not found.
|
133
|
+
def delete!(target)
|
134
|
+
delete(target) || (raise "No such middleware to remove: #{target.inspect}")
|
123
135
|
end
|
124
136
|
|
125
137
|
def move(target, source)
|
@@ -143,7 +155,7 @@ module ActionDispatch
|
|
143
155
|
def use(klass, *args, &block)
|
144
156
|
middlewares.push(build_middleware(klass, args, block))
|
145
157
|
end
|
146
|
-
ruby2_keywords(:use)
|
158
|
+
ruby2_keywords(:use)
|
147
159
|
|
148
160
|
def build(app = nil, &block)
|
149
161
|
instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
|
@@ -158,7 +170,7 @@ module ActionDispatch
|
|
158
170
|
|
159
171
|
private
|
160
172
|
def assert_index(index, where)
|
161
|
-
i = index.is_a?(Integer) ? index :
|
173
|
+
i = index.is_a?(Integer) ? index : index_of(index)
|
162
174
|
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
|
163
175
|
i
|
164
176
|
end
|
@@ -166,5 +178,11 @@ module ActionDispatch
|
|
166
178
|
def build_middleware(klass, args, block)
|
167
179
|
Middleware.new(klass, args, block)
|
168
180
|
end
|
181
|
+
|
182
|
+
def index_of(klass)
|
183
|
+
middlewares.index do |m|
|
184
|
+
m.name == klass.name
|
185
|
+
end
|
186
|
+
end
|
169
187
|
end
|
170
188
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rack/utils"
|
4
|
-
require "active_support/core_ext/uri"
|
5
4
|
|
6
5
|
module ActionDispatch
|
7
6
|
# This middleware serves static files from disk, if available.
|
@@ -25,7 +24,7 @@ module ActionDispatch
|
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
28
|
-
# This endpoint serves static files from disk using Rack::File
|
27
|
+
# This endpoint serves static files from disk using +Rack::File+.
|
29
28
|
#
|
30
29
|
# URL paths are matched with static files according to expected
|
31
30
|
# conventions: +path+, +path+.html, +path+/index.html.
|
@@ -34,13 +33,13 @@ module ActionDispatch
|
|
34
33
|
# and gzip (.gz) files are supported. If +path+.br exists, this
|
35
34
|
# endpoint returns that file with a <tt>Content-Encoding: br</tt> header.
|
36
35
|
#
|
37
|
-
# If no matching file is found, this endpoint responds 404 Not Found
|
36
|
+
# If no matching file is found, this endpoint responds <tt>404 Not Found</tt>.
|
38
37
|
#
|
39
38
|
# Pass the +root+ directory to search for matching files, an optional
|
40
39
|
# <tt>index: "index"</tt> to change the default +path+/index.html, and optional
|
41
40
|
# additional response headers.
|
42
41
|
class FileHandler
|
43
|
-
# Accept-Encoding value -> file extension
|
42
|
+
# +Accept-Encoding+ value -> file extension
|
44
43
|
PRECOMPRESSED = {
|
45
44
|
"br" => ".br",
|
46
45
|
"gzip" => ".gz",
|
@@ -137,11 +136,8 @@ module ActionDispatch
|
|
137
136
|
end
|
138
137
|
|
139
138
|
def file_readable?(path)
|
140
|
-
|
141
|
-
|
142
|
-
false
|
143
|
-
else
|
144
|
-
file_stat.file? && file_stat.readable?
|
139
|
+
file_path = File.join(@root, path.b)
|
140
|
+
File.file?(file_path) && File.readable?(file_path)
|
145
141
|
end
|
146
142
|
|
147
143
|
def compressible?(content_type)
|