actionpack 6.1.7.6 → 7.0.0.alpha1
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 +99 -584
- data/MIT-LICENSE +2 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller/asset_paths.rb +1 -1
- data/lib/abstract_controller/base.rb +7 -21
- data/lib/abstract_controller/caching/fragments.rb +2 -2
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +9 -8
- data/lib/abstract_controller/collector.rb +2 -2
- data/lib/abstract_controller/error.rb +1 -1
- data/lib/abstract_controller/helpers.rb +3 -2
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/translation.rb +0 -2
- data/lib/abstract_controller/url_for.rb +4 -6
- data/lib/action_controller/api.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +3 -1
- data/lib/action_controller/metal/conditional_get.rb +38 -1
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- 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/http_authentication.rb +15 -16
- data/lib/action_controller/metal/instrumentation.rb +55 -52
- data/lib/action_controller/metal/live.rb +42 -2
- data/lib/action_controller/metal/mime_responds.rb +3 -3
- data/lib/action_controller/metal/params_wrapper.rb +7 -7
- data/lib/action_controller/metal/permissions_policy.rb +1 -1
- data/lib/action_controller/metal/query_tags.rb +16 -0
- data/lib/action_controller/metal/redirecting.rb +49 -34
- data/lib/action_controller/metal/rendering.rb +9 -9
- data/lib/action_controller/metal/request_forgery_protection.rb +64 -20
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +1 -3
- data/lib/action_controller/metal/strong_parameters.rb +25 -29
- data/lib/action_controller/metal/testing.rb +0 -2
- data/lib/action_controller/metal.rb +7 -10
- data/lib/action_controller/railtie.rb +42 -5
- data/lib/action_controller/test_case.rb +6 -2
- 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 +47 -37
- data/lib/action_dispatch/http/filter_parameters.rb +5 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +13 -3
- data/lib/action_dispatch/http/mime_type.rb +9 -11
- data/lib/action_dispatch/http/parameters.rb +4 -4
- data/lib/action_dispatch/http/permissions_policy.rb +1 -1
- data/lib/action_dispatch/http/request.rb +10 -19
- data/lib/action_dispatch/http/response.rb +3 -3
- data/lib/action_dispatch/http/url.rb +9 -10
- 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 +5 -12
- 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 +27 -31
- 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 +1 -1
- data/lib/action_dispatch/middleware/flash.rb +9 -11
- data/lib/action_dispatch/middleware/host_authorization.rb +25 -73
- data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
- data/lib/action_dispatch/middleware/show_exceptions.rb +6 -18
- data/lib/action_dispatch/middleware/stack.rb +50 -9
- data/lib/action_dispatch/middleware/static.rb +2 -5
- 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 +2 -2
- 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/mapper.rb +44 -72
- data/lib/action_dispatch/routing/redirection.rb +0 -2
- data/lib/action_dispatch/routing/route_set.rb +7 -4
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
- data/lib/action_dispatch/routing/url_for.rb +1 -2
- data/lib/action_dispatch/routing.rb +2 -2
- data/lib/action_dispatch/system_test_case.rb +6 -12
- data/lib/action_dispatch/system_testing/driver.rb +24 -4
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +10 -6
- 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 +2 -2
- data/lib/action_dispatch.rb +1 -1
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +21 -21
@@ -68,7 +68,7 @@ function highlight_state(index, color) {
|
|
68
68
|
}
|
69
69
|
|
70
70
|
function highlight_finish(index) {
|
71
|
-
svg_nodes[index].select('
|
71
|
+
svg_nodes[index].select('ellipse')
|
72
72
|
.style("fill", "while")
|
73
73
|
.transition().duration(500)
|
74
74
|
.style("fill", "blue");
|
@@ -76,54 +76,79 @@ function highlight_finish(index) {
|
|
76
76
|
|
77
77
|
function match(input) {
|
78
78
|
reset_graph();
|
79
|
-
var table
|
80
|
-
var states
|
81
|
-
var regexp_states
|
82
|
-
var string_states
|
83
|
-
var
|
79
|
+
var table = tt();
|
80
|
+
var states = [[0, null]];
|
81
|
+
var regexp_states = table['regexp_states'];
|
82
|
+
var string_states = table['string_states'];
|
83
|
+
var stdparam_states = table['stdparam_states'];
|
84
|
+
var accepting = table['accepting'];
|
85
|
+
var default_re = new RegExp("^[^.\/?]+$");
|
86
|
+
var start_index = 0;
|
84
87
|
|
85
88
|
highlight_state(0);
|
86
89
|
|
87
90
|
tokenize(input, function(token) {
|
91
|
+
var end_index = start_index + token.length;
|
92
|
+
|
88
93
|
var new_states = [];
|
89
94
|
for(var key in states) {
|
90
|
-
var
|
95
|
+
var state_parts = states[key];
|
96
|
+
var state = state_parts[0];
|
97
|
+
var previous_start = state_parts[1];
|
98
|
+
|
99
|
+
if(previous_start == null) {
|
100
|
+
if(string_states[state] && string_states[state][token]) {
|
101
|
+
var new_state = string_states[state][token];
|
102
|
+
highlight_edge(state, new_state);
|
103
|
+
highlight_state(new_state);
|
104
|
+
new_states.push([new_state, null]);
|
105
|
+
}
|
91
106
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
107
|
+
if(stdparam_states[state] && default_re.test(token)) {
|
108
|
+
for(var key in stdparam_states[state]) {
|
109
|
+
var new_state = stdparam_states[state][key];
|
110
|
+
highlight_edge(state, new_state);
|
111
|
+
highlight_state(new_state);
|
112
|
+
new_states.push([new_state, null]);
|
113
|
+
}
|
114
|
+
}
|
97
115
|
}
|
98
116
|
|
99
117
|
if(regexp_states[state]) {
|
118
|
+
var slice_start = previous_start != null ? previous_start : start_index;
|
119
|
+
|
100
120
|
for(var key in regexp_states[state]) {
|
101
121
|
var re = new RegExp("^" + key + "$");
|
102
|
-
|
122
|
+
|
123
|
+
var accumulation = input.slice(slice_start, end_index);
|
124
|
+
|
125
|
+
if(re.test(accumulation)) {
|
103
126
|
var new_state = regexp_states[state][key];
|
104
127
|
highlight_edge(state, new_state);
|
105
128
|
highlight_state(new_state);
|
106
|
-
new_states.push(new_state);
|
129
|
+
new_states.push([new_state, null]);
|
107
130
|
}
|
131
|
+
|
132
|
+
// retry the same regexp with the accumulated data either way
|
133
|
+
new_states.push([state, slice_start]);
|
108
134
|
}
|
109
135
|
}
|
110
136
|
}
|
111
137
|
|
112
|
-
if(new_states.length == 0) {
|
113
|
-
return;
|
114
|
-
}
|
115
138
|
states = new_states;
|
139
|
+
start_index = end_index;
|
116
140
|
});
|
117
141
|
|
118
142
|
for(var key in states) {
|
119
|
-
var
|
143
|
+
var state_parts = states[key];
|
144
|
+
var state = state_parts[0];
|
145
|
+
var slice_start = state_parts[1];
|
146
|
+
|
147
|
+
// we must ignore ones that are still accepting more data
|
148
|
+
if (slice_start != null) continue;
|
149
|
+
|
120
150
|
if(accepting[state]) {
|
121
|
-
|
122
|
-
if(!regexp_states[mkey] && !string_states[mkey]) {
|
123
|
-
highlight_edge(state, mkey);
|
124
|
-
highlight_finish(mkey);
|
125
|
-
}
|
126
|
-
}
|
151
|
+
highlight_finish(state);
|
127
152
|
} else {
|
128
153
|
highlight_state(state, "red");
|
129
154
|
}
|
@@ -8,7 +8,7 @@
|
|
8
8
|
<%= style %>
|
9
9
|
<% end %>
|
10
10
|
</style>
|
11
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"
|
11
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js"></script>
|
12
12
|
</head>
|
13
13
|
<body>
|
14
14
|
<div id="wrapper">
|
@@ -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,6 +88,10 @@ module ActionDispatch
|
|
88
88
|
# :startdoc:
|
89
89
|
end
|
90
90
|
|
91
|
+
ActiveSupport.on_load(:action_dispatch_request) do
|
92
|
+
include RequestCookieMethods
|
93
|
+
end
|
94
|
+
|
91
95
|
# Read and write data to cookies through ActionController#cookies.
|
92
96
|
#
|
93
97
|
# When reading cookie data, the data is read from the HTTP request header, 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.
|
@@ -280,9 +284,23 @@ module ActionDispatch
|
|
280
284
|
end
|
281
285
|
end
|
282
286
|
|
283
|
-
class CookieJar
|
287
|
+
class CookieJar # :nodoc:
|
284
288
|
include Enumerable, ChainedCookieJars
|
285
289
|
|
290
|
+
# This regular expression is used to split the levels of a domain.
|
291
|
+
# The top level domain can be any string without a period or
|
292
|
+
# **.**, ***.** style TLDs like co.uk or com.au
|
293
|
+
#
|
294
|
+
# www.example.co.uk gives:
|
295
|
+
# $& => example.co.uk
|
296
|
+
#
|
297
|
+
# example.com gives:
|
298
|
+
# $& => example.com
|
299
|
+
#
|
300
|
+
# lots.of.subdomains.example.local gives:
|
301
|
+
# $& => example.local
|
302
|
+
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
|
303
|
+
|
286
304
|
def self.build(req, cookies)
|
287
305
|
jar = new(req)
|
288
306
|
jar.update(cookies)
|
@@ -435,35 +453,13 @@ module ActionDispatch
|
|
435
453
|
options[:same_site] ||= cookies_same_site_protection.call(request)
|
436
454
|
|
437
455
|
if options[:domain] == :all || options[:domain] == "all"
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
# Case where request.host is not an IP address or it's an invalid domain
|
442
|
-
# (ip confirms to the domain structure we expect so we explicitly check for ip)
|
443
|
-
if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1
|
444
|
-
options[:domain] = nil
|
445
|
-
return
|
446
|
-
end
|
447
|
-
|
448
|
-
# If there is a provided tld length then we use it otherwise default domain.
|
449
|
-
if options[:tld_length].present?
|
450
|
-
# Case where the tld_length provided is valid
|
451
|
-
if dot_splitted_host.length >= options[:tld_length]
|
452
|
-
cookie_domain = dot_splitted_host.last(options[:tld_length]).join('.')
|
453
|
-
end
|
454
|
-
# Case where tld_length is not provided
|
455
|
-
else
|
456
|
-
# Regular TLDs
|
457
|
-
if !(/\.[^.]{2,3}\.[^.]{2}\z/.match?(request.host))
|
458
|
-
cookie_domain = dot_splitted_host.last(2).join(".")
|
459
|
-
# **.**, ***.** style TLDs like co.uk and com.au
|
460
|
-
else
|
461
|
-
cookie_domain = dot_splitted_host.last(3).join('.')
|
462
|
-
end
|
463
|
-
end
|
456
|
+
# If there is a provided tld length then we use it otherwise default domain regexp.
|
457
|
+
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
|
464
458
|
|
465
|
-
|
466
|
-
|
459
|
+
# If host is not ip and matches domain regexp.
|
460
|
+
# (ip confirms to domain regexp so we explicitly check for ip)
|
461
|
+
options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
|
462
|
+
".#{$&}"
|
467
463
|
end
|
468
464
|
elsif options[:domain].is_a? Array
|
469
465
|
# 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)
|
@@ -59,16 +59,14 @@ module ActionDispatch
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def commit_flash # :nodoc:
|
62
|
-
session
|
63
|
-
flash_hash = self.flash_hash
|
62
|
+
return unless session.enabled?
|
64
63
|
|
65
64
|
if flash_hash && (flash_hash.present? || session.key?("flash"))
|
66
65
|
session["flash"] = flash_hash.to_session_value
|
67
66
|
self.flash = flash_hash.dup
|
68
67
|
end
|
69
68
|
|
70
|
-
if
|
71
|
-
session.key?("flash") && session["flash"].nil?
|
69
|
+
if session.loaded? && session.key?("flash") && session["flash"].nil?
|
72
70
|
session.delete("flash")
|
73
71
|
end
|
74
72
|
end
|
@@ -79,7 +77,7 @@ module ActionDispatch
|
|
79
77
|
end
|
80
78
|
end
|
81
79
|
|
82
|
-
class FlashNow
|
80
|
+
class FlashNow # :nodoc:
|
83
81
|
attr_accessor :flash
|
84
82
|
|
85
83
|
def initialize(flash)
|
@@ -111,7 +109,7 @@ module ActionDispatch
|
|
111
109
|
class FlashHash
|
112
110
|
include Enumerable
|
113
111
|
|
114
|
-
def self.from_session_value(value)
|
112
|
+
def self.from_session_value(value) # :nodoc:
|
115
113
|
case value
|
116
114
|
when FlashHash # Rails 3.1, 3.2
|
117
115
|
flashes = value.instance_variable_get(:@flashes)
|
@@ -132,13 +130,13 @@ module ActionDispatch
|
|
132
130
|
|
133
131
|
# Builds a hash containing the flashes to keep for the next request.
|
134
132
|
# If there are none to keep, returns +nil+.
|
135
|
-
def to_session_value
|
133
|
+
def to_session_value # :nodoc:
|
136
134
|
flashes_to_keep = @flashes.except(*@discard)
|
137
135
|
return nil if flashes_to_keep.empty?
|
138
136
|
{ "discard" => [], "flashes" => flashes_to_keep }
|
139
137
|
end
|
140
138
|
|
141
|
-
def initialize(flashes = {}, discard = [])
|
139
|
+
def initialize(flashes = {}, discard = []) # :nodoc:
|
142
140
|
@discard = Set.new(stringify_array(discard))
|
143
141
|
@flashes = flashes.stringify_keys
|
144
142
|
@now = nil
|
@@ -162,7 +160,7 @@ module ActionDispatch
|
|
162
160
|
@flashes[k.to_s]
|
163
161
|
end
|
164
162
|
|
165
|
-
def update(h)
|
163
|
+
def update(h) # :nodoc:
|
166
164
|
@discard.subtract stringify_array(h.keys)
|
167
165
|
@flashes.update h.stringify_keys
|
168
166
|
self
|
@@ -202,7 +200,7 @@ module ActionDispatch
|
|
202
200
|
|
203
201
|
alias :merge! :update
|
204
202
|
|
205
|
-
def replace(h)
|
203
|
+
def replace(h) # :nodoc:
|
206
204
|
@discard.clear
|
207
205
|
@flashes.replace h.stringify_keys
|
208
206
|
self
|
@@ -253,7 +251,7 @@ module ActionDispatch
|
|
253
251
|
# Mark for removal entries that were kept, and delete unkept ones.
|
254
252
|
#
|
255
253
|
# This method is called automatically by filters, so you generally don't need to care about it.
|
256
|
-
def sweep
|
254
|
+
def sweep # :nodoc:
|
257
255
|
@discard.each { |k| @flashes.delete k }
|
258
256
|
@discard.replace @flashes.keys
|
259
257
|
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
|
@@ -13,22 +11,8 @@ module ActionDispatch
|
|
13
11
|
#
|
14
12
|
# When a request comes to an unauthorized host, the +response_app+
|
15
13
|
# application will be executed and rendered. If no +response_app+ is given, a
|
16
|
-
# default one will run
|
17
|
-
# The default response app logs blocked host info with level 'error' and
|
18
|
-
# responds with <tt>403 Forbidden</tt>. The body of the response contains debug info
|
19
|
-
# if +config.consider_all_requests_local+ is set to true, otherwise the body is empty.
|
14
|
+
# default one will run, which responds with <tt>403 Forbidden</tt>.
|
20
15
|
class HostAuthorization
|
21
|
-
ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")]
|
22
|
-
PORT_REGEX = /(?::\d+)/ # :nodoc:
|
23
|
-
IPV4_HOSTNAME = /(?<host>\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc:
|
24
|
-
IPV6_HOSTNAME = /(?<host>[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc:
|
25
|
-
IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc:
|
26
|
-
VALID_IP_HOSTNAME = Regexp.union( # :nodoc:
|
27
|
-
/\A#{IPV4_HOSTNAME}\z/,
|
28
|
-
/\A#{IPV6_HOSTNAME}\z/,
|
29
|
-
/\A#{IPV6_HOSTNAME_WITH_PORT}\z/,
|
30
|
-
)
|
31
|
-
|
32
16
|
class Permissions # :nodoc:
|
33
17
|
def initialize(hosts)
|
34
18
|
@hosts = sanitize_hosts(hosts)
|
@@ -40,17 +24,11 @@ module ActionDispatch
|
|
40
24
|
|
41
25
|
def allows?(host)
|
42
26
|
@hosts.any? do |allowed|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# IP. Treat similar errors as blocked access.
|
49
|
-
false
|
50
|
-
end
|
51
|
-
else
|
52
|
-
allowed === host
|
53
|
-
end
|
27
|
+
allowed === host
|
28
|
+
rescue
|
29
|
+
# IPAddr#=== raises an error if you give it a hostname instead of
|
30
|
+
# IP. Treat similar errors as blocked access.
|
31
|
+
false
|
54
32
|
end
|
55
33
|
end
|
56
34
|
|
@@ -66,59 +44,29 @@ module ActionDispatch
|
|
66
44
|
end
|
67
45
|
|
68
46
|
def sanitize_regexp(host)
|
69
|
-
/\A#{host}
|
47
|
+
/\A#{host}\z/
|
70
48
|
end
|
71
49
|
|
72
50
|
def sanitize_string(host)
|
73
51
|
if host.start_with?(".")
|
74
|
-
/\A(
|
52
|
+
/\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/i
|
75
53
|
else
|
76
|
-
/\A#{Regexp.escape host}
|
54
|
+
/\A#{Regexp.escape host}\z/i
|
77
55
|
end
|
78
56
|
end
|
79
|
-
|
80
|
-
def extract_hostname(host)
|
81
|
-
host.slice(VALID_IP_HOSTNAME, "host") || host
|
82
|
-
end
|
83
57
|
end
|
84
58
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
def call(env)
|
89
|
-
request = Request.new(env)
|
90
|
-
format = request.xhr? ? "text/plain" : "text/html"
|
91
|
-
|
92
|
-
log_error(request)
|
93
|
-
response(format, response_body(request))
|
94
|
-
end
|
95
|
-
|
96
|
-
private
|
97
|
-
def response_body(request)
|
98
|
-
return "" unless request.get_header("action_dispatch.show_detailed_exceptions")
|
99
|
-
|
100
|
-
template = DebugView.new(host: request.host)
|
101
|
-
template.render(template: "rescues/blocked_host", layout: "rescues/layout")
|
102
|
-
end
|
103
|
-
|
104
|
-
def response(format, body)
|
105
|
-
[RESPONSE_STATUS,
|
106
|
-
{ "Content-Type" => "#{format}; charset=#{Response.default_charset}",
|
107
|
-
"Content-Length" => body.bytesize.to_s },
|
108
|
-
[body]]
|
109
|
-
end
|
110
|
-
|
111
|
-
def log_error(request)
|
112
|
-
logger = available_logger(request)
|
113
|
-
|
114
|
-
return unless logger
|
59
|
+
DEFAULT_RESPONSE_APP = -> env do
|
60
|
+
request = Request.new(env)
|
115
61
|
|
116
|
-
|
117
|
-
|
62
|
+
format = request.xhr? ? "text/plain" : "text/html"
|
63
|
+
template = DebugView.new(host: request.host)
|
64
|
+
body = template.render(template: "rescues/blocked_host", layout: "rescues/layout")
|
118
65
|
|
119
|
-
|
120
|
-
|
121
|
-
|
66
|
+
[403, {
|
67
|
+
"Content-Type" => "#{format}; charset=#{Response.default_charset}",
|
68
|
+
"Content-Length" => body.bytesize.to_s,
|
69
|
+
}, [body]]
|
122
70
|
end
|
123
71
|
|
124
72
|
def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
|
@@ -135,7 +83,7 @@ module ActionDispatch
|
|
135
83
|
response_app ||= deprecated_response_app
|
136
84
|
end
|
137
85
|
|
138
|
-
@response_app = response_app ||
|
86
|
+
@response_app = response_app || DEFAULT_RESPONSE_APP
|
139
87
|
end
|
140
88
|
|
141
89
|
def call(env)
|
@@ -152,9 +100,13 @@ module ActionDispatch
|
|
152
100
|
end
|
153
101
|
|
154
102
|
private
|
103
|
+
HOSTNAME = /[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]/i
|
104
|
+
VALID_ORIGIN_HOST = /\A(#{HOSTNAME})(?::\d+)?\z/
|
105
|
+
VALID_FORWARDED_HOST = /(?:\A|,[ ]?)(#{HOSTNAME})(?::\d+)?\z/
|
106
|
+
|
155
107
|
def authorized?(request)
|
156
|
-
origin_host = request.get_header("HTTP_HOST")
|
157
|
-
forwarded_host = request.x_forwarded_host&.
|
108
|
+
origin_host = request.get_header("HTTP_HOST")&.slice(VALID_ORIGIN_HOST, 1) || ""
|
109
|
+
forwarded_host = request.x_forwarded_host&.slice(VALID_FORWARDED_HOST, 1) || ""
|
158
110
|
|
159
111
|
@permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host))
|
160
112
|
end
|
@@ -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
|
@@ -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" \
|
@@ -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
|
@@ -47,23 +40,18 @@ module ActionDispatch
|
|
47
40
|
request.set_header "action_dispatch.exception", wrapper.unwrapped_exception
|
48
41
|
request.set_header "action_dispatch.original_path", request.path_info
|
49
42
|
request.set_header "action_dispatch.original_request_method", request.raw_request_method
|
50
|
-
fallback_to_html_format_if_invalid_mime_type(request)
|
51
43
|
request.path_info = "/#{status}"
|
52
44
|
request.request_method = "GET"
|
53
45
|
response = @exceptions_app.call(request.env)
|
54
46
|
response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
|
55
47
|
rescue Exception => failsafe_error
|
56
48
|
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
57
|
-
FAILSAFE_RESPONSE
|
58
|
-
end
|
59
49
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
rescue ActionDispatch::Http::MimeNegotiation::InvalidType
|
66
|
-
request.set_header "HTTP_ACCEPT", "text/html"
|
50
|
+
[500, { "Content-Type" => "text/plain" },
|
51
|
+
["500 Internal Server Error\n" \
|
52
|
+
"If you are the administrator of this website, then please read this web " \
|
53
|
+
"application's log file and/or the web server's log file to find out what " \
|
54
|
+
"went wrong."]]
|
67
55
|
end
|
68
56
|
|
69
57
|
def pass_response(status)
|