actionpack 5.2.7.1 → 6.1.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +427 -338
  3. data/MIT-LICENSE +1 -2
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller/base.rb +38 -4
  6. data/lib/abstract_controller/caching/fragments.rb +6 -22
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/callbacks.rb +14 -2
  9. data/lib/abstract_controller/collector.rb +5 -4
  10. data/lib/abstract_controller/helpers.rb +106 -90
  11. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  12. data/lib/abstract_controller/rendering.rb +9 -9
  13. data/lib/abstract_controller/translation.rb +11 -5
  14. data/lib/abstract_controller.rb +1 -0
  15. data/lib/action_controller/api.rb +4 -3
  16. data/lib/action_controller/base.rb +6 -9
  17. data/lib/action_controller/caching.rb +1 -3
  18. data/lib/action_controller/log_subscriber.rb +10 -7
  19. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  20. data/lib/action_controller/metal/conditional_get.rb +19 -5
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  22. data/lib/action_controller/metal/cookies.rb +3 -1
  23. data/lib/action_controller/metal/data_streaming.rb +6 -7
  24. data/lib/action_controller/metal/default_headers.rb +17 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  26. data/lib/action_controller/metal/exceptions.rb +56 -2
  27. data/lib/action_controller/metal/flash.rb +5 -5
  28. data/lib/action_controller/metal/head.rb +7 -4
  29. data/lib/action_controller/metal/helpers.rb +14 -5
  30. data/lib/action_controller/metal/http_authentication.rb +25 -23
  31. data/lib/action_controller/metal/implicit_render.rb +5 -15
  32. data/lib/action_controller/metal/instrumentation.rb +13 -14
  33. data/lib/action_controller/metal/live.rb +39 -32
  34. data/lib/action_controller/metal/logging.rb +20 -0
  35. data/lib/action_controller/metal/mime_responds.rb +19 -4
  36. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  37. data/lib/action_controller/metal/params_wrapper.rb +32 -22
  38. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  39. data/lib/action_controller/metal/redirecting.rb +26 -7
  40. data/lib/action_controller/metal/renderers.rb +4 -4
  41. data/lib/action_controller/metal/rendering.rb +8 -3
  42. data/lib/action_controller/metal/request_forgery_protection.rb +26 -49
  43. data/lib/action_controller/metal/rescue.rb +1 -1
  44. data/lib/action_controller/metal/streaming.rb +0 -1
  45. data/lib/action_controller/metal/strong_parameters.rb +168 -59
  46. data/lib/action_controller/metal/url_for.rb +1 -1
  47. data/lib/action_controller/metal.rb +10 -8
  48. data/lib/action_controller/railties/helpers.rb +1 -1
  49. data/lib/action_controller/renderer.rb +37 -13
  50. data/lib/action_controller/template_assertions.rb +1 -1
  51. data/lib/action_controller/test_case.rb +71 -63
  52. data/lib/action_controller.rb +7 -4
  53. data/lib/action_dispatch/http/cache.rb +32 -28
  54. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  55. data/lib/action_dispatch/http/content_security_policy.rb +34 -18
  56. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  57. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  58. data/lib/action_dispatch/http/headers.rb +4 -4
  59. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  60. data/lib/action_dispatch/http/mime_type.rb +43 -24
  61. data/lib/action_dispatch/http/parameters.rb +14 -23
  62. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  63. data/lib/action_dispatch/http/request.rb +45 -22
  64. data/lib/action_dispatch/http/response.rb +45 -25
  65. data/lib/action_dispatch/http/upload.rb +9 -1
  66. data/lib/action_dispatch/http/url.rb +82 -82
  67. data/lib/action_dispatch/journey/formatter.rb +55 -31
  68. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  69. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  70. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  71. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  72. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  73. data/lib/action_dispatch/journey/parser.rb +13 -13
  74. data/lib/action_dispatch/journey/parser.y +1 -1
  75. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  76. data/lib/action_dispatch/journey/route.rb +10 -20
  77. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  78. data/lib/action_dispatch/journey/router.rb +26 -34
  79. data/lib/action_dispatch/journey/routes.rb +0 -2
  80. data/lib/action_dispatch/journey/scanner.rb +10 -4
  81. data/lib/action_dispatch/journey/visitors.rb +1 -4
  82. data/lib/action_dispatch/journey.rb +0 -2
  83. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  84. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  85. data/lib/action_dispatch/middleware/cookies.rb +150 -123
  86. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  87. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  88. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  89. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  90. data/lib/action_dispatch/middleware/flash.rb +1 -1
  91. data/lib/action_dispatch/middleware/host_authorization.rb +170 -0
  92. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  93. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  94. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  95. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  96. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  97. data/lib/action_dispatch/middleware/show_exceptions.rb +13 -2
  98. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  99. data/lib/action_dispatch/middleware/stack.rb +56 -2
  100. data/lib/action_dispatch/middleware/static.rb +153 -93
  101. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  107. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  108. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  112. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  114. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  115. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  119. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  120. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  121. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  122. data/lib/action_dispatch/railtie.rb +8 -2
  123. data/lib/action_dispatch/request/session.rb +11 -10
  124. data/lib/action_dispatch/request/utils.rb +26 -2
  125. data/lib/action_dispatch/routing/inspector.rb +100 -52
  126. data/lib/action_dispatch/routing/mapper.rb +155 -103
  127. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  128. data/lib/action_dispatch/routing/redirection.rb +4 -4
  129. data/lib/action_dispatch/routing/route_set.rb +71 -69
  130. data/lib/action_dispatch/routing/url_for.rb +2 -2
  131. data/lib/action_dispatch/routing.rb +21 -20
  132. data/lib/action_dispatch/system_test_case.rb +60 -11
  133. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  134. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  135. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  136. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  137. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  138. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  139. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  140. data/lib/action_dispatch/testing/assertions.rb +1 -1
  141. data/lib/action_dispatch/testing/integration.rb +60 -28
  142. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  143. data/lib/action_dispatch/testing/test_process.rb +32 -4
  144. data/lib/action_dispatch/testing/test_request.rb +3 -3
  145. data/lib/action_dispatch/testing/test_response.rb +4 -32
  146. data/lib/action_dispatch.rb +9 -3
  147. data/lib/action_pack/gem_version.rb +3 -3
  148. data/lib/action_pack.rb +1 -1
  149. metadata +36 -23
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. 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.merge(env)
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.fetch(key, key.to_s)
127
+ RACK_KEY_TRANSLATION[key] || key.to_s
111
128
  end
112
129
 
113
130
  def rack_value_for(key, value)
114
- RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value
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 Rails 5.1.
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.dup
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
- generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action))
496
- generated_path = generated_path(generated_extras)
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 { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
604
- env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
605
- env.delete "action_dispatch.request.query_parameters"
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"
@@ -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".freeze
8
- HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze
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)
@@ -18,7 +18,7 @@ module ActionDispatch
18
18
  end
19
19
 
20
20
  def if_none_match_etags
21
- if_none_match ? if_none_match.split(/\s*,\s*/) : []
21
+ if_none_match ? if_none_match.split(",").each(&:strip!) : []
22
22
  end
23
23
 
24
24
  def not_modified?(modified_at)
@@ -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.starts_with?('W/"')
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
- DATE = "Date".freeze
128
- LAST_MODIFIED = "Last-Modified".freeze
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
- key = directive.tr("-", "_")
155
- cache_control[key.to_sym] = argument || true
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".freeze
170
- NO_CACHE = "no-cache".freeze
171
- PUBLIC = "public".freeze
172
- PRIVATE = "private".freeze
173
- MUST_REVALIDATE = "must-revalidate".freeze
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
- cc_headers = cache_control_headers
188
- if extras = cc_headers.delete(:extras)
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
- if control.empty?
198
- # Let middleware handle default behavior
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 = control[: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
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/object/deep_dup"
4
+ require "active_support/core_ext/array/wrap"
4
5
 
5
6
  module ActionDispatch #:nodoc:
6
7
  class ContentSecurityPolicy
7
8
  class Middleware
8
- CONTENT_TYPE = "Content-Type".freeze
9
- POLICY = "Content-Security-Policy".freeze
10
- POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
9
+ CONTENT_TYPE = "Content-Type"
10
+ POLICY = "Content-Security-Policy"
11
+ POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
11
12
 
12
13
  def initialize(app)
13
14
  @app = app
@@ -21,8 +22,9 @@ module ActionDispatch #:nodoc:
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
@@ -43,10 +45,11 @@ module ActionDispatch #:nodoc:
43
45
  end
44
46
 
45
47
  module Request
46
- POLICY = "action_dispatch.content_security_policy".freeze
47
- POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
48
- NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator".freeze
49
- NONCE = "action_dispatch.content_security_policy_nonce".freeze
48
+ POLICY = "action_dispatch.content_security_policy"
49
+ POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
50
+ NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
51
+ NONCE = "action_dispatch.content_security_policy_nonce"
52
+ NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives"
50
53
 
51
54
  def content_security_policy
52
55
  get_header(POLICY)
@@ -72,6 +75,14 @@ module ActionDispatch #:nodoc:
72
75
  set_header(NONCE_GENERATOR, generator)
73
76
  end
74
77
 
78
+ def content_security_policy_nonce_directives
79
+ get_header(NONCE_DIRECTIVES)
80
+ end
81
+
82
+ def content_security_policy_nonce_directives=(generator)
83
+ set_header(NONCE_DIRECTIVES, generator)
84
+ end
85
+
75
86
  def content_security_policy_nonce
76
87
  if content_security_policy_nonce_generator
77
88
  if nonce = get_header(NONCE)
@@ -83,7 +94,6 @@ module ActionDispatch #:nodoc:
83
94
  end
84
95
 
85
96
  private
86
-
87
97
  def generate_content_security_policy_nonce
88
98
  content_security_policy_nonce_generator.call(self)
89
99
  end
@@ -119,14 +129,19 @@ module ActionDispatch #:nodoc:
119
129
  manifest_src: "manifest-src",
120
130
  media_src: "media-src",
121
131
  object_src: "object-src",
132
+ prefetch_src: "prefetch-src",
122
133
  script_src: "script-src",
134
+ script_src_attr: "script-src-attr",
135
+ script_src_elem: "script-src-elem",
123
136
  style_src: "style-src",
137
+ style_src_attr: "style-src-attr",
138
+ style_src_elem: "style-src-elem",
124
139
  worker_src: "worker-src"
125
140
  }.freeze
126
141
 
127
- NONCE_DIRECTIVES = %w[script-src].freeze
142
+ DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
128
143
 
129
- private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
144
+ private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
130
145
 
131
146
  attr_reader :directives
132
147
 
@@ -195,8 +210,9 @@ module ActionDispatch #:nodoc:
195
210
  end
196
211
  end
197
212
 
198
- def build(context = nil, nonce = nil)
199
- build_directives(context, nonce).compact.join("; ")
213
+ def build(context = nil, nonce = nil, nonce_directives = nil)
214
+ nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
215
+ build_directives(context, nonce, nonce_directives).compact.join("; ")
200
216
  end
201
217
 
202
218
  private
@@ -219,10 +235,10 @@ module ActionDispatch #:nodoc:
219
235
  end
220
236
  end
221
237
 
222
- def build_directives(context, nonce)
238
+ def build_directives(context, nonce, nonce_directives)
223
239
  @directives.map do |directive, sources|
224
240
  if sources.is_a?(Array)
225
- if nonce && nonce_directive?(directive)
241
+ if nonce && nonce_directive?(directive, nonce_directives)
226
242
  "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
227
243
  else
228
244
  "#{directive} #{build_directive(sources, context).join(' ')}"
@@ -250,15 +266,15 @@ module ActionDispatch #:nodoc:
250
266
  raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
251
267
  else
252
268
  resolved = context.instance_exec(&source)
253
- resolved.is_a?(Symbol) ? apply_mapping(resolved) : resolved
269
+ apply_mappings(Array.wrap(resolved))
254
270
  end
255
271
  else
256
272
  raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
257
273
  end
258
274
  end
259
275
 
260
- def nonce_directive?(directive)
261
- NONCE_DIRECTIVES.include?(directive)
276
+ def nonce_directive?(directive, nonce_directives)
277
+ nonce_directives.include?(directive)
262
278
  end
263
279
  end
264
280
  end