actionpack 5.2.7.1 → 6.1.4.6
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 +329 -352
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/abstract_controller/base.rb +38 -4
- data/lib/abstract_controller/caching/fragments.rb +6 -22
- data/lib/abstract_controller/caching.rb +1 -1
- data/lib/abstract_controller/callbacks.rb +14 -2
- data/lib/abstract_controller/collector.rb +1 -2
- data/lib/abstract_controller/helpers.rb +106 -90
- data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +11 -5
- data/lib/abstract_controller.rb +1 -0
- data/lib/action_controller/api.rb +4 -3
- data/lib/action_controller/base.rb +6 -9
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/log_subscriber.rb +10 -7
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +19 -5
- data/lib/action_controller/metal/content_security_policy.rb +1 -2
- data/lib/action_controller/metal/cookies.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +6 -7
- data/lib/action_controller/metal/default_headers.rb +17 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
- data/lib/action_controller/metal/exceptions.rb +56 -2
- data/lib/action_controller/metal/flash.rb +5 -5
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +14 -5
- data/lib/action_controller/metal/http_authentication.rb +24 -23
- data/lib/action_controller/metal/implicit_render.rb +5 -15
- data/lib/action_controller/metal/instrumentation.rb +13 -14
- data/lib/action_controller/metal/live.rb +39 -32
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +19 -4
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +32 -22
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +6 -6
- data/lib/action_controller/metal/renderers.rb +4 -4
- data/lib/action_controller/metal/rendering.rb +8 -3
- data/lib/action_controller/metal/request_forgery_protection.rb +26 -49
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/streaming.rb +0 -1
- data/lib/action_controller/metal/strong_parameters.rb +167 -58
- data/lib/action_controller/metal/url_for.rb +1 -1
- data/lib/action_controller/metal.rb +10 -8
- data/lib/action_controller/railties/helpers.rb +1 -1
- data/lib/action_controller/renderer.rb +37 -13
- data/lib/action_controller/template_assertions.rb +1 -1
- data/lib/action_controller/test_case.rb +71 -63
- data/lib/action_controller.rb +7 -4
- data/lib/action_dispatch/http/cache.rb +31 -27
- data/lib/action_dispatch/http/content_disposition.rb +45 -0
- data/lib/action_dispatch/http/content_security_policy.rb +39 -17
- data/lib/action_dispatch/http/filter_parameters.rb +9 -8
- data/lib/action_dispatch/http/filter_redirect.rb +2 -3
- data/lib/action_dispatch/http/headers.rb +4 -4
- data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
- data/lib/action_dispatch/http/mime_type.rb +43 -24
- data/lib/action_dispatch/http/parameters.rb +14 -23
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +45 -22
- data/lib/action_dispatch/http/response.rb +45 -25
- data/lib/action_dispatch/http/upload.rb +9 -1
- data/lib/action_dispatch/http/url.rb +82 -82
- data/lib/action_dispatch/journey/formatter.rb +55 -31
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +13 -11
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +19 -21
- data/lib/action_dispatch/journey/route.rb +10 -20
- data/lib/action_dispatch/journey/router/utils.rb +14 -12
- data/lib/action_dispatch/journey/router.rb +26 -34
- data/lib/action_dispatch/journey/routes.rb +0 -2
- data/lib/action_dispatch/journey/scanner.rb +10 -4
- data/lib/action_dispatch/journey/visitors.rb +1 -4
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/callbacks.rb +2 -4
- data/lib/action_dispatch/middleware/cookies.rb +128 -109
- data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
- data/lib/action_dispatch/middleware/debug_view.rb +66 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
- data/lib/action_dispatch/middleware/flash.rb +1 -1
- data/lib/action_dispatch/middleware/host_authorization.rb +141 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
- data/lib/action_dispatch/middleware/request_id.rb +5 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
- data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
- data/lib/action_dispatch/middleware/ssl.rb +20 -15
- data/lib/action_dispatch/middleware/stack.rb +56 -2
- data/lib/action_dispatch/middleware/static.rb +153 -93
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
- data/lib/action_dispatch/railtie.rb +8 -2
- data/lib/action_dispatch/request/session.rb +11 -10
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing/inspector.rb +100 -52
- data/lib/action_dispatch/routing/mapper.rb +155 -103
- data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
- data/lib/action_dispatch/routing/redirection.rb +4 -4
- data/lib/action_dispatch/routing/route_set.rb +71 -69
- data/lib/action_dispatch/routing/url_for.rb +2 -2
- data/lib/action_dispatch/routing.rb +21 -20
- data/lib/action_dispatch/system_test_case.rb +54 -11
- data/lib/action_dispatch/system_testing/browser.rb +53 -16
- data/lib/action_dispatch/system_testing/driver.rb +11 -3
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
- data/lib/action_dispatch/testing/assertion_response.rb +0 -1
- data/lib/action_dispatch/testing/assertions/response.rb +4 -7
- data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +60 -28
- data/lib/action_dispatch/testing/request_encoder.rb +2 -2
- data/lib/action_dispatch/testing/test_process.rb +29 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch/testing/test_response.rb +4 -32
- data/lib/action_dispatch.rb +9 -3
- data/lib/action_pack/gem_version.rb +4 -4
- data/lib/action_pack.rb +1 -1
- metadata +35 -23
- data/lib/action_controller/metal/force_ssl.rb +0 -99
- data/lib/action_dispatch/http/parameter_filter.rb +0 -86
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/hash/keys"
|
4
|
-
|
5
3
|
module ActionController
|
6
4
|
# ActionController::Renderer allows you to render arbitrary templates
|
7
5
|
# without requirement of being in controller actions.
|
@@ -67,10 +65,29 @@ module ActionController
|
|
67
65
|
def initialize(controller, env, defaults)
|
68
66
|
@controller = controller
|
69
67
|
@defaults = defaults
|
70
|
-
@env = normalize_keys defaults
|
68
|
+
@env = normalize_keys defaults, env
|
71
69
|
end
|
72
70
|
|
73
71
|
# Render templates with any options from ActionController::Base#render_to_string.
|
72
|
+
#
|
73
|
+
# The primary options are:
|
74
|
+
# * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt> for details.
|
75
|
+
# * <tt>:file</tt> - Renders an explicit template file. Add <tt>:locals</tt> to pass in, if so desired.
|
76
|
+
# It shouldn’t be used directly with unsanitized user input due to lack of validation.
|
77
|
+
# * <tt>:inline</tt> - Renders an ERB template string.
|
78
|
+
# * <tt>:plain</tt> - Renders provided text and sets the content type as <tt>text/plain</tt>.
|
79
|
+
# * <tt>:html</tt> - Renders the provided HTML safe string, otherwise
|
80
|
+
# performs HTML escape on the string first. Sets the content type as <tt>text/html</tt>.
|
81
|
+
# * <tt>:json</tt> - Renders the provided hash or object in JSON. You don't
|
82
|
+
# need to call <tt>.to_json</tt> on the object you want to render.
|
83
|
+
# * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>.
|
84
|
+
#
|
85
|
+
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
|
86
|
+
#
|
87
|
+
# If an object responding to +render_in+ is passed, +render_in+ is called on the object,
|
88
|
+
# passing in the current view context.
|
89
|
+
#
|
90
|
+
# Otherwise, a partial is rendered using the second parameter as the locals hash.
|
74
91
|
def render(*args)
|
75
92
|
raise "missing controller" unless controller
|
76
93
|
|
@@ -82,11 +99,18 @@ module ActionController
|
|
82
99
|
instance.set_response! controller.make_response!(request)
|
83
100
|
instance.render_to_string(*args)
|
84
101
|
end
|
102
|
+
alias_method :render_to_string, :render # :nodoc:
|
85
103
|
|
86
104
|
private
|
87
|
-
def normalize_keys(env)
|
105
|
+
def normalize_keys(defaults, env)
|
88
106
|
new_env = {}
|
89
107
|
env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
|
108
|
+
|
109
|
+
defaults.each_pair do |k, v|
|
110
|
+
key = rack_key_for(k)
|
111
|
+
new_env[key] = rack_value_for(k, v) unless new_env.key?(key)
|
112
|
+
end
|
113
|
+
|
90
114
|
new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
|
91
115
|
new_env
|
92
116
|
end
|
@@ -99,19 +123,19 @@ module ActionController
|
|
99
123
|
input: "rack.input"
|
100
124
|
}
|
101
125
|
|
102
|
-
IDENTITY = ->(_) { _ }
|
103
|
-
|
104
|
-
RACK_VALUE_TRANSLATION = {
|
105
|
-
https: ->(v) { v ? "on" : "off" },
|
106
|
-
method: ->(v) { v.upcase },
|
107
|
-
}
|
108
|
-
|
109
126
|
def rack_key_for(key)
|
110
|
-
RACK_KEY_TRANSLATION
|
127
|
+
RACK_KEY_TRANSLATION[key] || key.to_s
|
111
128
|
end
|
112
129
|
|
113
130
|
def rack_value_for(key, value)
|
114
|
-
|
131
|
+
case key
|
132
|
+
when :https
|
133
|
+
value ? "on" : "off"
|
134
|
+
when :method
|
135
|
+
-value.upcase
|
136
|
+
else
|
137
|
+
value
|
138
|
+
end
|
115
139
|
end
|
116
140
|
end
|
117
141
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionController
|
4
|
-
module TemplateAssertions
|
4
|
+
module TemplateAssertions # :nodoc:
|
5
5
|
def assert_template(options = {}, message = nil)
|
6
6
|
raise NoMethodError,
|
7
7
|
"assert_template has been extracted to a gem. To continue using it,
|
@@ -24,9 +24,12 @@ module ActionController
|
|
24
24
|
def new_controller_thread # :nodoc:
|
25
25
|
yield
|
26
26
|
end
|
27
|
+
|
28
|
+
# Avoid a deadlock from the queue filling up
|
29
|
+
Buffer.queue_size = nil
|
27
30
|
end
|
28
31
|
|
29
|
-
# ActionController::TestCase will be deprecated and moved to a gem in
|
32
|
+
# ActionController::TestCase will be deprecated and moved to a gem in the future.
|
30
33
|
# Please use ActionDispatch::IntegrationTest going forward.
|
31
34
|
class TestRequest < ActionDispatch::TestRequest #:nodoc:
|
32
35
|
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
|
@@ -84,7 +87,7 @@ module ActionController
|
|
84
87
|
value = value.to_param
|
85
88
|
end
|
86
89
|
|
87
|
-
path_parameters[key] = value
|
90
|
+
path_parameters[key.to_sym] = value
|
88
91
|
end
|
89
92
|
end
|
90
93
|
|
@@ -158,7 +161,6 @@ module ActionController
|
|
158
161
|
end.new
|
159
162
|
|
160
163
|
private
|
161
|
-
|
162
164
|
def params_parsers
|
163
165
|
super.merge @custom_param_parsers
|
164
166
|
end
|
@@ -203,12 +205,16 @@ module ActionController
|
|
203
205
|
clear
|
204
206
|
end
|
205
207
|
|
208
|
+
def dig(*keys)
|
209
|
+
keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key }
|
210
|
+
@data.dig(*keys)
|
211
|
+
end
|
212
|
+
|
206
213
|
def fetch(key, *args, &block)
|
207
214
|
@data.fetch(key.to_s, *args, &block)
|
208
215
|
end
|
209
216
|
|
210
217
|
private
|
211
|
-
|
212
218
|
def load!
|
213
219
|
@id
|
214
220
|
end
|
@@ -276,9 +282,6 @@ module ActionController
|
|
276
282
|
# after calling +post+. If the various assert methods are not sufficient, then you
|
277
283
|
# may use this object to inspect the HTTP response in detail.
|
278
284
|
#
|
279
|
-
# (Earlier versions of \Rails required each functional test to subclass
|
280
|
-
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
|
281
|
-
#
|
282
285
|
# == Controller is automatically inferred
|
283
286
|
#
|
284
287
|
# ActionController::TestCase will automatically infer the controller under test
|
@@ -460,7 +463,7 @@ module ActionController
|
|
460
463
|
def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
|
461
464
|
check_required_ivars
|
462
465
|
|
463
|
-
action = action.to_s
|
466
|
+
action = +action.to_s
|
464
467
|
http_method = method.to_s.upcase
|
465
468
|
|
466
469
|
@html_document = nil
|
@@ -492,57 +495,8 @@ module ActionController
|
|
492
495
|
parameters[:format] = format
|
493
496
|
end
|
494
497
|
|
495
|
-
|
496
|
-
|
497
|
-
query_string_keys = query_parameter_names(generated_extras)
|
498
|
-
|
499
|
-
@request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
|
500
|
-
|
501
|
-
@request.session.update(session) if session
|
502
|
-
@request.flash.update(flash || {})
|
503
|
-
|
504
|
-
if xhr
|
505
|
-
@request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
|
506
|
-
@request.fetch_header("HTTP_ACCEPT") do |k|
|
507
|
-
@request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
@request.fetch_header("SCRIPT_NAME") do |k|
|
512
|
-
@request.set_header k, @controller.config.relative_url_root
|
513
|
-
end
|
514
|
-
|
515
|
-
begin
|
516
|
-
@controller.recycle!
|
517
|
-
@controller.dispatch(action, @request, @response)
|
518
|
-
ensure
|
519
|
-
@request = @controller.request
|
520
|
-
@response = @controller.response
|
521
|
-
|
522
|
-
if @request.have_cookie_jar?
|
523
|
-
unless @request.cookie_jar.committed?
|
524
|
-
@request.cookie_jar.write(@response)
|
525
|
-
cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
|
526
|
-
end
|
527
|
-
end
|
528
|
-
@response.prepare!
|
529
|
-
|
530
|
-
if flash_value = @request.flash.to_session_value
|
531
|
-
@request.session["flash"] = flash_value
|
532
|
-
else
|
533
|
-
@request.session.delete("flash")
|
534
|
-
end
|
535
|
-
|
536
|
-
if xhr
|
537
|
-
@request.delete_header "HTTP_X_REQUESTED_WITH"
|
538
|
-
@request.delete_header "HTTP_ACCEPT"
|
539
|
-
end
|
540
|
-
@request.query_string = ""
|
541
|
-
|
542
|
-
@response.sent!
|
543
|
-
end
|
544
|
-
|
545
|
-
@response
|
498
|
+
setup_request(controller_class_name, action, parameters, session, flash, xhr)
|
499
|
+
process_controller_response(action, cookies, xhr)
|
546
500
|
end
|
547
501
|
|
548
502
|
def controller_class_name
|
@@ -598,12 +552,66 @@ module ActionController
|
|
598
552
|
end
|
599
553
|
|
600
554
|
private
|
555
|
+
def setup_request(controller_class_name, action, parameters, session, flash, xhr)
|
556
|
+
generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action))
|
557
|
+
generated_path = generated_path(generated_extras)
|
558
|
+
query_string_keys = query_parameter_names(generated_extras)
|
559
|
+
|
560
|
+
@request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
|
561
|
+
|
562
|
+
@request.session.update(session) if session
|
563
|
+
@request.flash.update(flash || {})
|
564
|
+
|
565
|
+
if xhr
|
566
|
+
@request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
|
567
|
+
@request.fetch_header("HTTP_ACCEPT") do |k|
|
568
|
+
@request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
@request.fetch_header("SCRIPT_NAME") do |k|
|
573
|
+
@request.set_header k, @controller.config.relative_url_root
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
def process_controller_response(action, cookies, xhr)
|
578
|
+
begin
|
579
|
+
@controller.recycle!
|
580
|
+
@controller.dispatch(action, @request, @response)
|
581
|
+
ensure
|
582
|
+
@request = @controller.request
|
583
|
+
@response = @controller.response
|
584
|
+
|
585
|
+
if @request.have_cookie_jar?
|
586
|
+
unless @request.cookie_jar.committed?
|
587
|
+
@request.cookie_jar.write(@response)
|
588
|
+
cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
|
589
|
+
end
|
590
|
+
end
|
591
|
+
@response.prepare!
|
592
|
+
|
593
|
+
if flash_value = @request.flash.to_session_value
|
594
|
+
@request.session["flash"] = flash_value
|
595
|
+
else
|
596
|
+
@request.session.delete("flash")
|
597
|
+
end
|
598
|
+
|
599
|
+
if xhr
|
600
|
+
@request.delete_header "HTTP_X_REQUESTED_WITH"
|
601
|
+
@request.delete_header "HTTP_ACCEPT"
|
602
|
+
end
|
603
|
+
@request.query_string = ""
|
604
|
+
|
605
|
+
@response.sent!
|
606
|
+
end
|
607
|
+
|
608
|
+
@response
|
609
|
+
end
|
601
610
|
|
602
611
|
def scrub_env!(env)
|
603
|
-
env.delete_if
|
604
|
-
|
605
|
-
|
606
|
-
env.delete "action_dispatch.request.request_parameters"
|
612
|
+
env.delete_if do |k, _|
|
613
|
+
k.start_with?("rack.request", "action_dispatch.request", "action_dispatch.rescue")
|
614
|
+
end
|
607
615
|
env["rack.input"] = StringIO.new
|
608
616
|
env.delete "CONTENT_LENGTH"
|
609
617
|
env.delete "RAW_POST_DATA"
|
data/lib/action_controller.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/rails"
|
4
3
|
require "abstract_controller"
|
5
4
|
require "action_dispatch"
|
6
|
-
require "action_controller/metal/live"
|
7
5
|
require "action_controller/metal/strong_parameters"
|
8
6
|
|
9
7
|
module ActionController
|
@@ -12,7 +10,6 @@ module ActionController
|
|
12
10
|
autoload :API
|
13
11
|
autoload :Base
|
14
12
|
autoload :Metal
|
15
|
-
autoload :Middleware
|
16
13
|
autoload :Renderer
|
17
14
|
autoload :FormBuilder
|
18
15
|
|
@@ -21,20 +18,26 @@ module ActionController
|
|
21
18
|
end
|
22
19
|
|
23
20
|
autoload_under "metal" do
|
21
|
+
eager_autoload do
|
22
|
+
autoload :Live
|
23
|
+
end
|
24
|
+
|
24
25
|
autoload :ConditionalGet
|
25
26
|
autoload :ContentSecurityPolicy
|
26
27
|
autoload :Cookies
|
27
28
|
autoload :DataStreaming
|
29
|
+
autoload :DefaultHeaders
|
28
30
|
autoload :EtagWithTemplateDigest
|
29
31
|
autoload :EtagWithFlash
|
32
|
+
autoload :PermissionsPolicy
|
30
33
|
autoload :Flash
|
31
|
-
autoload :ForceSSL
|
32
34
|
autoload :Head
|
33
35
|
autoload :Helpers
|
34
36
|
autoload :HttpAuthentication
|
35
37
|
autoload :BasicImplicitRender
|
36
38
|
autoload :ImplicitRender
|
37
39
|
autoload :Instrumentation
|
40
|
+
autoload :Logging
|
38
41
|
autoload :MimeResponds
|
39
42
|
autoload :ParamsWrapper
|
40
43
|
autoload :Redirecting
|
@@ -4,8 +4,8 @@ module ActionDispatch
|
|
4
4
|
module Http
|
5
5
|
module Cache
|
6
6
|
module Request
|
7
|
-
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
|
8
|
-
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
|
7
|
+
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
|
8
|
+
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
|
9
9
|
|
10
10
|
def if_modified_since
|
11
11
|
if since = get_header(HTTP_IF_MODIFIED_SINCE)
|
@@ -114,7 +114,7 @@ module ActionDispatch
|
|
114
114
|
|
115
115
|
# True if an ETag is set and it's a weak validator (preceded with W/)
|
116
116
|
def weak_etag?
|
117
|
-
etag? && etag.
|
117
|
+
etag? && etag.start_with?('W/"')
|
118
118
|
end
|
119
119
|
|
120
120
|
# True if an ETag is set and it isn't a weak validator (not preceded with W/)
|
@@ -123,10 +123,9 @@ module ActionDispatch
|
|
123
123
|
end
|
124
124
|
|
125
125
|
private
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
|
126
|
+
DATE = "Date"
|
127
|
+
LAST_MODIFIED = "Last-Modified"
|
128
|
+
SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate])
|
130
129
|
|
131
130
|
def generate_weak_etag(validators)
|
132
131
|
"W/#{generate_strong_etag(validators)}"
|
@@ -151,8 +150,8 @@ module ActionDispatch
|
|
151
150
|
directive, argument = segment.split("=", 2)
|
152
151
|
|
153
152
|
if SPECIAL_KEYS.include? directive
|
154
|
-
|
155
|
-
cache_control[
|
153
|
+
directive.tr!("-", "_")
|
154
|
+
cache_control[directive.to_sym] = argument || true
|
156
155
|
else
|
157
156
|
cache_control[:extras] ||= []
|
158
157
|
cache_control[:extras] << segment
|
@@ -166,11 +165,12 @@ module ActionDispatch
|
|
166
165
|
@cache_control = cache_control_headers
|
167
166
|
end
|
168
167
|
|
169
|
-
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
168
|
+
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
169
|
+
NO_STORE = "no-store"
|
170
|
+
NO_CACHE = "no-cache"
|
171
|
+
PUBLIC = "public"
|
172
|
+
PRIVATE = "private"
|
173
|
+
MUST_REVALIDATE = "must-revalidate"
|
174
174
|
|
175
175
|
def handle_conditional_get!
|
176
176
|
# Normally default cache control setting is handled by ETag
|
@@ -183,38 +183,42 @@ module ActionDispatch
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def merge_and_normalize_cache_control!(cache_control)
|
186
|
-
control =
|
187
|
-
|
188
|
-
if
|
186
|
+
control = cache_control_headers
|
187
|
+
|
188
|
+
return if control.empty? && cache_control.empty? # Let middleware handle default behavior
|
189
|
+
|
190
|
+
if extras = control.delete(:extras)
|
189
191
|
cache_control[:extras] ||= []
|
190
192
|
cache_control[:extras] += extras
|
191
193
|
cache_control[:extras].uniq!
|
192
194
|
end
|
193
195
|
|
194
|
-
control.merge! cc_headers
|
195
196
|
control.merge! cache_control
|
196
197
|
|
197
|
-
|
198
|
-
|
198
|
+
options = []
|
199
|
+
|
200
|
+
if control[:no_store]
|
201
|
+
options << PRIVATE if control[:private]
|
202
|
+
options << NO_STORE
|
199
203
|
elsif control[:no_cache]
|
200
|
-
options = []
|
201
204
|
options << PUBLIC if control[:public]
|
202
205
|
options << NO_CACHE
|
203
206
|
options.concat(control[:extras]) if control[:extras]
|
204
|
-
|
205
|
-
self._cache_control = options.join(", ")
|
206
207
|
else
|
207
|
-
extras
|
208
|
+
extras = control[:extras]
|
208
209
|
max_age = control[:max_age]
|
210
|
+
stale_while_revalidate = control[:stale_while_revalidate]
|
211
|
+
stale_if_error = control[:stale_if_error]
|
209
212
|
|
210
|
-
options = []
|
211
213
|
options << "max-age=#{max_age.to_i}" if max_age
|
212
214
|
options << (control[:public] ? PUBLIC : PRIVATE)
|
213
215
|
options << MUST_REVALIDATE if control[:must_revalidate]
|
216
|
+
options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
|
217
|
+
options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
|
214
218
|
options.concat(extras) if extras
|
215
|
-
|
216
|
-
self._cache_control = options.join(", ")
|
217
219
|
end
|
220
|
+
|
221
|
+
self._cache_control = options.join(", ")
|
218
222
|
end
|
219
223
|
end
|
220
224
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionDispatch
|
4
|
+
module Http
|
5
|
+
class ContentDisposition # :nodoc:
|
6
|
+
def self.format(disposition:, filename:)
|
7
|
+
new(disposition: disposition, filename: filename).to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :disposition, :filename
|
11
|
+
|
12
|
+
def initialize(disposition:, filename:)
|
13
|
+
@disposition = disposition
|
14
|
+
@filename = filename
|
15
|
+
end
|
16
|
+
|
17
|
+
TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!\#$+.^_`|~-]/
|
18
|
+
|
19
|
+
def ascii_filename
|
20
|
+
'filename="' + percent_escape(I18n.transliterate(filename), TRADITIONAL_ESCAPED_CHAR) + '"'
|
21
|
+
end
|
22
|
+
|
23
|
+
RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!\#$&+.^_`|~-]/
|
24
|
+
|
25
|
+
def utf8_filename
|
26
|
+
"filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
if filename
|
31
|
+
"#{disposition}; #{ascii_filename}; #{utf8_filename}"
|
32
|
+
else
|
33
|
+
"#{disposition}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def percent_escape(string, pattern)
|
39
|
+
string.gsub(pattern) do |char|
|
40
|
+
char.bytes.map { |byte| "%%%02X" % byte }.join
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -5,9 +5,9 @@ require "active_support/core_ext/object/deep_dup"
|
|
5
5
|
module ActionDispatch #:nodoc:
|
6
6
|
class ContentSecurityPolicy
|
7
7
|
class Middleware
|
8
|
-
CONTENT_TYPE = "Content-Type"
|
9
|
-
POLICY = "Content-Security-Policy"
|
10
|
-
POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
|
8
|
+
CONTENT_TYPE = "Content-Type"
|
9
|
+
POLICY = "Content-Security-Policy"
|
10
|
+
POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
|
11
11
|
|
12
12
|
def initialize(app)
|
13
13
|
@app = app
|
@@ -17,18 +17,26 @@ module ActionDispatch #:nodoc:
|
|
17
17
|
request = ActionDispatch::Request.new env
|
18
18
|
_, headers, _ = response = @app.call(env)
|
19
19
|
|
20
|
+
return response unless html_response?(headers)
|
20
21
|
return response if policy_present?(headers)
|
21
22
|
|
22
23
|
if policy = request.content_security_policy
|
23
24
|
nonce = request.content_security_policy_nonce
|
25
|
+
nonce_directives = request.content_security_policy_nonce_directives
|
24
26
|
context = request.controller_instance || request
|
25
|
-
headers[header_name(request)] = policy.build(context, nonce)
|
27
|
+
headers[header_name(request)] = policy.build(context, nonce, nonce_directives)
|
26
28
|
end
|
27
29
|
|
28
30
|
response
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
34
|
+
def html_response?(headers)
|
35
|
+
if content_type = headers[CONTENT_TYPE]
|
36
|
+
/html/.match?(content_type)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
32
40
|
def header_name(request)
|
33
41
|
if request.content_security_policy_report_only
|
34
42
|
POLICY_REPORT_ONLY
|
@@ -43,10 +51,11 @@ module ActionDispatch #:nodoc:
|
|
43
51
|
end
|
44
52
|
|
45
53
|
module Request
|
46
|
-
POLICY = "action_dispatch.content_security_policy"
|
47
|
-
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
|
48
|
-
NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
|
49
|
-
NONCE = "action_dispatch.content_security_policy_nonce"
|
54
|
+
POLICY = "action_dispatch.content_security_policy"
|
55
|
+
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
|
56
|
+
NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
|
57
|
+
NONCE = "action_dispatch.content_security_policy_nonce"
|
58
|
+
NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives"
|
50
59
|
|
51
60
|
def content_security_policy
|
52
61
|
get_header(POLICY)
|
@@ -72,6 +81,14 @@ module ActionDispatch #:nodoc:
|
|
72
81
|
set_header(NONCE_GENERATOR, generator)
|
73
82
|
end
|
74
83
|
|
84
|
+
def content_security_policy_nonce_directives
|
85
|
+
get_header(NONCE_DIRECTIVES)
|
86
|
+
end
|
87
|
+
|
88
|
+
def content_security_policy_nonce_directives=(generator)
|
89
|
+
set_header(NONCE_DIRECTIVES, generator)
|
90
|
+
end
|
91
|
+
|
75
92
|
def content_security_policy_nonce
|
76
93
|
if content_security_policy_nonce_generator
|
77
94
|
if nonce = get_header(NONCE)
|
@@ -83,7 +100,6 @@ module ActionDispatch #:nodoc:
|
|
83
100
|
end
|
84
101
|
|
85
102
|
private
|
86
|
-
|
87
103
|
def generate_content_security_policy_nonce
|
88
104
|
content_security_policy_nonce_generator.call(self)
|
89
105
|
end
|
@@ -119,14 +135,19 @@ module ActionDispatch #:nodoc:
|
|
119
135
|
manifest_src: "manifest-src",
|
120
136
|
media_src: "media-src",
|
121
137
|
object_src: "object-src",
|
138
|
+
prefetch_src: "prefetch-src",
|
122
139
|
script_src: "script-src",
|
140
|
+
script_src_attr: "script-src-attr",
|
141
|
+
script_src_elem: "script-src-elem",
|
123
142
|
style_src: "style-src",
|
143
|
+
style_src_attr: "style-src-attr",
|
144
|
+
style_src_elem: "style-src-elem",
|
124
145
|
worker_src: "worker-src"
|
125
146
|
}.freeze
|
126
147
|
|
127
|
-
|
148
|
+
DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
|
128
149
|
|
129
|
-
private_constant :MAPPINGS, :DIRECTIVES, :
|
150
|
+
private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
|
130
151
|
|
131
152
|
attr_reader :directives
|
132
153
|
|
@@ -195,8 +216,9 @@ module ActionDispatch #:nodoc:
|
|
195
216
|
end
|
196
217
|
end
|
197
218
|
|
198
|
-
def build(context = nil, nonce = nil)
|
199
|
-
|
219
|
+
def build(context = nil, nonce = nil, nonce_directives = nil)
|
220
|
+
nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
|
221
|
+
build_directives(context, nonce, nonce_directives).compact.join("; ")
|
200
222
|
end
|
201
223
|
|
202
224
|
private
|
@@ -219,10 +241,10 @@ module ActionDispatch #:nodoc:
|
|
219
241
|
end
|
220
242
|
end
|
221
243
|
|
222
|
-
def build_directives(context, nonce)
|
244
|
+
def build_directives(context, nonce, nonce_directives)
|
223
245
|
@directives.map do |directive, sources|
|
224
246
|
if sources.is_a?(Array)
|
225
|
-
if nonce && nonce_directive?(directive)
|
247
|
+
if nonce && nonce_directive?(directive, nonce_directives)
|
226
248
|
"#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
|
227
249
|
else
|
228
250
|
"#{directive} #{build_directive(sources, context).join(' ')}"
|
@@ -257,8 +279,8 @@ module ActionDispatch #:nodoc:
|
|
257
279
|
end
|
258
280
|
end
|
259
281
|
|
260
|
-
def nonce_directive?(directive)
|
261
|
-
|
282
|
+
def nonce_directive?(directive, nonce_directives)
|
283
|
+
nonce_directives.include?(directive)
|
262
284
|
end
|
263
285
|
end
|
264
286
|
end
|