actionpack 6.1.7 → 7.0.4.1
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 +269 -406
- data/MIT-LICENSE +1 -0
- data/README.rdoc +2 -3
- 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 +4 -3
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/translation.rb +3 -2
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/action_controller/api.rb +6 -6
- 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/conditional_get.rb +39 -2
- 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 +5 -13
- data/lib/action_controller/metal/exceptions.rb +19 -30
- data/lib/action_controller/metal/flash.rb +6 -2
- 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 +93 -18
- data/lib/action_controller/metal/renderers.rb +10 -11
- data/lib/action_controller/metal/rendering.rb +8 -8
- data/lib/action_controller/metal/request_forgery_protection.rb +78 -29
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +6 -8
- data/lib/action_controller/metal/strong_parameters.rb +100 -54
- data/lib/action_controller/metal/testing.rb +9 -2
- data/lib/action_controller/metal/url_for.rb +3 -3
- data/lib/action_controller/metal.rb +10 -13
- data/lib/action_controller/railtie.rb +49 -6
- data/lib/action_controller/renderer.rb +1 -1
- data/lib/action_controller/test_case.rb +28 -7
- data/lib/action_controller.rb +2 -5
- data/lib/action_dispatch/http/cache.rb +14 -7
- data/lib/action_dispatch/http/content_security_policy.rb +108 -35
- data/lib/action_dispatch/http/filter_parameters.rb +5 -0
- 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 +12 -21
- data/lib/action_dispatch/http/response.rb +3 -16
- 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 +42 -27
- 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 +1 -12
- data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- 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 +7 -9
- data/lib/action_dispatch/middleware/stack.rb +27 -9
- data/lib/action_dispatch/middleware/static.rb +2 -6
- 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 +3 -2
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +2 -0
- 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 +5 -14
- 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 +59 -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 +4 -5
- 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 +2 -12
- data/lib/action_dispatch/system_testing/driver.rb +35 -11
- 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/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
@@ -7,7 +7,7 @@ require "active_support/json"
|
|
7
7
|
require "rack/utils"
|
8
8
|
|
9
9
|
module ActionDispatch
|
10
|
-
|
10
|
+
module RequestCookieMethods
|
11
11
|
def cookie_jar
|
12
12
|
fetch_header("action_dispatch.cookies") do
|
13
13
|
self.cookie_jar = Cookies::CookieJar.build(self, cookies)
|
@@ -88,7 +88,11 @@ module ActionDispatch
|
|
88
88
|
# :startdoc:
|
89
89
|
end
|
90
90
|
|
91
|
-
|
91
|
+
ActiveSupport.on_load(:action_dispatch_request) do
|
92
|
+
include RequestCookieMethods
|
93
|
+
end
|
94
|
+
|
95
|
+
# Read and write data to cookies through ActionController::Base#cookies.
|
92
96
|
#
|
93
97
|
# When reading cookie data, the data is read from the HTTP request header, Cookie.
|
94
98
|
# When writing cookie data, the data is sent out in the HTTP response header, Set-Cookie.
|
@@ -99,7 +103,7 @@ module ActionDispatch
|
|
99
103
|
# # This cookie will be deleted when the user's browser is closed.
|
100
104
|
# cookies[:user_name] = "david"
|
101
105
|
#
|
102
|
-
# # Cookie values are String
|
106
|
+
# # Cookie values are String-based. Other data types need to be serialized.
|
103
107
|
# cookies[:lat_lon] = JSON.generate([47.68, -122.37])
|
104
108
|
#
|
105
109
|
# # Sets a cookie that expires in 1 hour.
|
@@ -135,7 +139,7 @@ module ActionDispatch
|
|
135
139
|
#
|
136
140
|
# cookies.delete :user_name
|
137
141
|
#
|
138
|
-
# Please note that if you specify a
|
142
|
+
# Please note that if you specify a +:domain+ when setting a cookie, you must also specify the domain when deleting the cookie:
|
139
143
|
#
|
140
144
|
# cookies[:name] = {
|
141
145
|
# value: 'a yummy cookie',
|
@@ -172,6 +176,9 @@ module ActionDispatch
|
|
172
176
|
# Default is +false+.
|
173
177
|
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
|
174
178
|
# only HTTP. Defaults to +false+.
|
179
|
+
# * <tt>:same_site</tt> - The value of the +SameSite+ cookie attribute, which
|
180
|
+
# determines how this cookie should be restricted in cross-site contexts.
|
181
|
+
# Possible values are +:none+, +:lax+, and +:strict+. Defaults to +:lax+.
|
175
182
|
class Cookies
|
176
183
|
HTTP_HEADER = "Set-Cookie"
|
177
184
|
GENERATOR_KEY = "action_dispatch.key_generator"
|
@@ -195,7 +202,7 @@ module ActionDispatch
|
|
195
202
|
# Raised when storing more than 4K of session data.
|
196
203
|
CookieOverflow = Class.new StandardError
|
197
204
|
|
198
|
-
# Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
|
205
|
+
# Include in a cookie jar to allow chaining, e.g. +cookies.permanent.signed+.
|
199
206
|
module ChainedCookieJars
|
200
207
|
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
|
201
208
|
#
|
@@ -280,23 +287,9 @@ module ActionDispatch
|
|
280
287
|
end
|
281
288
|
end
|
282
289
|
|
283
|
-
class CookieJar
|
290
|
+
class CookieJar # :nodoc:
|
284
291
|
include Enumerable, ChainedCookieJars
|
285
292
|
|
286
|
-
# This regular expression is used to split the levels of a domain.
|
287
|
-
# The top level domain can be any string without a period or
|
288
|
-
# **.**, ***.** style TLDs like co.uk or com.au
|
289
|
-
#
|
290
|
-
# www.example.co.uk gives:
|
291
|
-
# $& => example.co.uk
|
292
|
-
#
|
293
|
-
# example.com gives:
|
294
|
-
# $& => example.com
|
295
|
-
#
|
296
|
-
# lots.of.subdomains.example.local gives:
|
297
|
-
# $& => example.local
|
298
|
-
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
|
299
|
-
|
300
293
|
def self.build(req, cookies)
|
301
294
|
jar = new(req)
|
302
295
|
jar.update(cookies)
|
@@ -435,7 +428,7 @@ module ActionDispatch
|
|
435
428
|
end
|
436
429
|
|
437
430
|
def write_cookie?(cookie)
|
438
|
-
request.ssl? || !cookie[:secure] || always_write_cookie
|
431
|
+
request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
|
439
432
|
end
|
440
433
|
|
441
434
|
def handle_options(options)
|
@@ -449,13 +442,35 @@ module ActionDispatch
|
|
449
442
|
options[:same_site] ||= cookies_same_site_protection.call(request)
|
450
443
|
|
451
444
|
if options[:domain] == :all || options[:domain] == "all"
|
452
|
-
|
453
|
-
|
445
|
+
cookie_domain = ""
|
446
|
+
dot_splitted_host = request.host.split('.', -1)
|
447
|
+
|
448
|
+
# Case where request.host is not an IP address or it's an invalid domain
|
449
|
+
# (ip confirms to the domain structure we expect so we explicitly check for ip)
|
450
|
+
if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1
|
451
|
+
options[:domain] = nil
|
452
|
+
return
|
453
|
+
end
|
454
|
+
|
455
|
+
# If there is a provided tld length then we use it otherwise default domain.
|
456
|
+
if options[:tld_length].present?
|
457
|
+
# Case where the tld_length provided is valid
|
458
|
+
if dot_splitted_host.length >= options[:tld_length]
|
459
|
+
cookie_domain = dot_splitted_host.last(options[:tld_length]).join('.')
|
460
|
+
end
|
461
|
+
# Case where tld_length is not provided
|
462
|
+
else
|
463
|
+
# Regular TLDs
|
464
|
+
if !(/([^.]{2,3}\.[^.]{2})$/.match?(request.host))
|
465
|
+
cookie_domain = dot_splitted_host.last(2).join('.')
|
466
|
+
# **.**, ***.** style TLDs like co.uk and com.au
|
467
|
+
else
|
468
|
+
cookie_domain = dot_splitted_host.last(3).join('.')
|
469
|
+
end
|
470
|
+
end
|
454
471
|
|
455
|
-
|
456
|
-
|
457
|
-
options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
|
458
|
-
".#{$&}"
|
472
|
+
options[:domain] = if cookie_domain.present?
|
473
|
+
".#{cookie_domain}"
|
459
474
|
end
|
460
475
|
elsif options[:domain].is_a? Array
|
461
476
|
# If host matches one of the supplied domains.
|
@@ -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
|
@@ -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
|
|
@@ -51,10 +51,8 @@ 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
|
-
# single string, IPAddr, or Regexp object is provided, it will be used in
|
57
|
-
# addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
|
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
|
58
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
|
@@ -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
|
@@ -5,7 +5,7 @@ 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
|
8
|
+
# through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends
|
9
9
|
# the same id to the client via the X-Request-Id header.
|
10
10
|
#
|
11
11
|
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
|
@@ -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,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
|
|
6
5
|
module ActionDispatch
|
@@ -15,14 +14,8 @@ module ActionDispatch
|
|
15
14
|
# If the application returns a "X-Cascade" pass response, this middleware
|
16
15
|
# will send an empty response as result with the correct status code.
|
17
16
|
# If any exception happens inside the exceptions app, this middleware
|
18
|
-
# catches the exceptions and returns a
|
17
|
+
# catches the exceptions and returns a failsafe response.
|
19
18
|
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
19
|
def initialize(app, exceptions_app)
|
27
20
|
@app = app
|
28
21
|
@exceptions_app = exceptions_app
|
@@ -54,7 +47,12 @@ module ActionDispatch
|
|
54
47
|
response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
|
55
48
|
rescue Exception => failsafe_error
|
56
49
|
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
57
|
-
|
50
|
+
|
51
|
+
[500, { "Content-Type" => "text/plain" },
|
52
|
+
["500 Internal Server Error\n" \
|
53
|
+
"If you are the administrator of this website, then please read this web " \
|
54
|
+
"application's log file and/or the web server's log file to find out what " \
|
55
|
+
"went wrong."]]
|
58
56
|
end
|
59
57
|
|
60
58
|
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
|