actionpack 5.2.8.1 → 6.1.6.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +383 -346
  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 +6 -6
  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 +31 -27
  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 +128 -109
  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 +34 -21
  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)
@@ -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