actionpack 6.1.7.5 → 7.0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +323 -399
- 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 +27 -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 +95 -22
- 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)
|