actionpack 5.2.1 → 7.0.2.4

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -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,36 +183,49 @@ 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)
189
- cache_control[:extras] ||= []
190
- cache_control[:extras] += extras
191
- cache_control[:extras].uniq!
186
+ control = cache_control_headers
187
+
188
+ return if control.empty? && cache_control.empty? # Let middleware handle default behavior
189
+
190
+ if cache_control.any?
191
+ # Any caching directive coming from a controller overrides
192
+ # no-cache/no-store in the default Cache-Control header.
193
+ control.delete(:no_cache)
194
+ control.delete(:no_store)
195
+
196
+ if extras = control.delete(:extras)
197
+ cache_control[:extras] ||= []
198
+ cache_control[:extras] += extras
199
+ cache_control[:extras].uniq!
200
+ end
201
+
202
+ control.merge! cache_control
192
203
  end
193
204
 
194
- control.merge! cc_headers
195
- control.merge! cache_control
205
+ options = []
196
206
 
197
- if control.empty?
198
- # Let middleware handle default behavior
207
+ if control[:no_store]
208
+ options << PRIVATE if control[:private]
209
+ options << NO_STORE
199
210
  elsif control[:no_cache]
200
- self._cache_control = NO_CACHE
201
- if control[:extras]
202
- self._cache_control = _cache_control + ", #{control[:extras].join(', ')}"
203
- end
211
+ options << PUBLIC if control[:public]
212
+ options << NO_CACHE
213
+ options.concat(control[:extras]) if control[:extras]
204
214
  else
205
- extras = control[:extras]
215
+ extras = control[:extras]
206
216
  max_age = control[:max_age]
217
+ stale_while_revalidate = control[:stale_while_revalidate]
218
+ stale_if_error = control[:stale_if_error]
207
219
 
208
- options = []
209
220
  options << "max-age=#{max_age.to_i}" if max_age
210
221
  options << (control[:public] ? PUBLIC : PRIVATE)
211
222
  options << MUST_REVALIDATE if control[:must_revalidate]
223
+ options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
224
+ options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
212
225
  options.concat(extras) if extras
213
-
214
- self._cache_control = options.join(", ")
215
226
  end
227
+
228
+ self._cache_control = options.join(", ")
216
229
  end
217
230
  end
218
231
  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
@@ -2,12 +2,12 @@
2
2
 
3
3
  require "active_support/core_ext/object/deep_dup"
4
4
 
5
- module ActionDispatch #:nodoc:
5
+ module ActionDispatch # :nodoc:
6
6
  class ContentSecurityPolicy
7
7
  class Middleware
8
- CONTENT_TYPE = "Content-Type".freeze
9
- POLICY = "Content-Security-Policy".freeze
10
- POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
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,25 +17,19 @@ 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)
21
20
  return response if policy_present?(headers)
22
21
 
23
22
  if policy = request.content_security_policy
24
23
  nonce = request.content_security_policy_nonce
25
- headers[header_name(request)] = policy.build(request.controller_instance, nonce)
24
+ nonce_directives = request.content_security_policy_nonce_directives
25
+ context = request.controller_instance || request
26
+ headers[header_name(request)] = policy.build(context, nonce, nonce_directives)
26
27
  end
27
28
 
28
29
  response
29
30
  end
30
31
 
31
32
  private
32
-
33
- def html_response?(headers)
34
- if content_type = headers[CONTENT_TYPE]
35
- content_type =~ /html/
36
- end
37
- end
38
-
39
33
  def header_name(request)
40
34
  if request.content_security_policy_report_only
41
35
  POLICY_REPORT_ONLY
@@ -50,10 +44,11 @@ module ActionDispatch #:nodoc:
50
44
  end
51
45
 
52
46
  module Request
53
- POLICY = "action_dispatch.content_security_policy".freeze
54
- POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
55
- NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator".freeze
56
- NONCE = "action_dispatch.content_security_policy_nonce".freeze
47
+ POLICY = "action_dispatch.content_security_policy"
48
+ POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
49
+ NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
50
+ NONCE = "action_dispatch.content_security_policy_nonce"
51
+ NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives"
57
52
 
58
53
  def content_security_policy
59
54
  get_header(POLICY)
@@ -79,6 +74,14 @@ module ActionDispatch #:nodoc:
79
74
  set_header(NONCE_GENERATOR, generator)
80
75
  end
81
76
 
77
+ def content_security_policy_nonce_directives
78
+ get_header(NONCE_DIRECTIVES)
79
+ end
80
+
81
+ def content_security_policy_nonce_directives=(generator)
82
+ set_header(NONCE_DIRECTIVES, generator)
83
+ end
84
+
82
85
  def content_security_policy_nonce
83
86
  if content_security_policy_nonce_generator
84
87
  if nonce = get_header(NONCE)
@@ -90,50 +93,58 @@ module ActionDispatch #:nodoc:
90
93
  end
91
94
 
92
95
  private
93
-
94
96
  def generate_content_security_policy_nonce
95
97
  content_security_policy_nonce_generator.call(self)
96
98
  end
97
99
  end
98
100
 
99
101
  MAPPINGS = {
100
- self: "'self'",
101
- unsafe_eval: "'unsafe-eval'",
102
- unsafe_inline: "'unsafe-inline'",
103
- none: "'none'",
104
- http: "http:",
105
- https: "https:",
106
- data: "data:",
107
- mediastream: "mediastream:",
108
- blob: "blob:",
109
- filesystem: "filesystem:",
110
- report_sample: "'report-sample'",
111
- strict_dynamic: "'strict-dynamic'",
112
- ws: "ws:",
113
- wss: "wss:"
102
+ self: "'self'",
103
+ unsafe_eval: "'unsafe-eval'",
104
+ unsafe_inline: "'unsafe-inline'",
105
+ none: "'none'",
106
+ http: "http:",
107
+ https: "https:",
108
+ data: "data:",
109
+ mediastream: "mediastream:",
110
+ allow_duplicates: "'allow-duplicates'",
111
+ blob: "blob:",
112
+ filesystem: "filesystem:",
113
+ report_sample: "'report-sample'",
114
+ script: "'script'",
115
+ strict_dynamic: "'strict-dynamic'",
116
+ ws: "ws:",
117
+ wss: "wss:"
114
118
  }.freeze
115
119
 
116
120
  DIRECTIVES = {
117
- base_uri: "base-uri",
118
- child_src: "child-src",
119
- connect_src: "connect-src",
120
- default_src: "default-src",
121
- font_src: "font-src",
122
- form_action: "form-action",
123
- frame_ancestors: "frame-ancestors",
124
- frame_src: "frame-src",
125
- img_src: "img-src",
126
- manifest_src: "manifest-src",
127
- media_src: "media-src",
128
- object_src: "object-src",
129
- script_src: "script-src",
130
- style_src: "style-src",
131
- worker_src: "worker-src"
121
+ base_uri: "base-uri",
122
+ child_src: "child-src",
123
+ connect_src: "connect-src",
124
+ default_src: "default-src",
125
+ font_src: "font-src",
126
+ form_action: "form-action",
127
+ frame_ancestors: "frame-ancestors",
128
+ frame_src: "frame-src",
129
+ img_src: "img-src",
130
+ manifest_src: "manifest-src",
131
+ media_src: "media-src",
132
+ object_src: "object-src",
133
+ prefetch_src: "prefetch-src",
134
+ require_trusted_types_for: "require-trusted-types-for",
135
+ script_src: "script-src",
136
+ script_src_attr: "script-src-attr",
137
+ script_src_elem: "script-src-elem",
138
+ style_src: "style-src",
139
+ style_src_attr: "style-src-attr",
140
+ style_src_elem: "style-src-elem",
141
+ trusted_types: "trusted-types",
142
+ worker_src: "worker-src"
132
143
  }.freeze
133
144
 
134
- NONCE_DIRECTIVES = %w[script-src].freeze
145
+ DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
135
146
 
136
- private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
147
+ private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
137
148
 
138
149
  attr_reader :directives
139
150
 
@@ -202,8 +213,9 @@ module ActionDispatch #:nodoc:
202
213
  end
203
214
  end
204
215
 
205
- def build(context = nil, nonce = nil)
206
- build_directives(context, nonce).compact.join("; ")
216
+ def build(context = nil, nonce = nil, nonce_directives = nil)
217
+ nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
218
+ build_directives(context, nonce, nonce_directives).compact.join("; ")
207
219
  end
208
220
 
209
221
  private
@@ -226,10 +238,10 @@ module ActionDispatch #:nodoc:
226
238
  end
227
239
  end
228
240
 
229
- def build_directives(context, nonce)
241
+ def build_directives(context, nonce, nonce_directives)
230
242
  @directives.map do |directive, sources|
231
243
  if sources.is_a?(Array)
232
- if nonce && nonce_directive?(directive)
244
+ if nonce && nonce_directive?(directive, nonce_directives)
233
245
  "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
234
246
  else
235
247
  "#{directive} #{build_directive(sources, context).join(' ')}"
@@ -256,15 +268,16 @@ module ActionDispatch #:nodoc:
256
268
  if context.nil?
257
269
  raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
258
270
  else
259
- context.instance_exec(&source)
271
+ resolved = context.instance_exec(&source)
272
+ resolved.is_a?(Symbol) ? apply_mapping(resolved) : resolved
260
273
  end
261
274
  else
262
275
  raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
263
276
  end
264
277
  end
265
278
 
266
- def nonce_directive?(directive)
267
- NONCE_DIRECTIVES.include?(directive)
279
+ def nonce_directive?(directive, nonce_directives)
280
+ nonce_directives.include?(directive)
268
281
  end
269
282
  end
270
283
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch/http/parameter_filter"
3
+ require "active_support/parameter_filter"
4
4
 
5
5
  module ActionDispatch
6
6
  module Http
@@ -9,8 +9,8 @@ module ActionDispatch
9
9
  # sub-hashes of the params hash to filter. Filtering only certain sub-keys
10
10
  # from a hash is possible by using the dot notation: 'credit_card.number'.
11
11
  # If a block is given, each key and value of the params hash and all
12
- # sub-hashes is passed to it, where the value or the key can be replaced using
13
- # String#replace or similar method.
12
+ # sub-hashes are passed to it, where the value or the key can be replaced using
13
+ # String#replace or similar methods.
14
14
  #
15
15
  # env["action_dispatch.parameter_filter"] = [:password]
16
16
  # => replaces the value to all keys matching /password/i with "[FILTERED]"
@@ -18,18 +18,23 @@ module ActionDispatch
18
18
  # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
19
19
  # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
20
20
  #
21
+ # env["action_dispatch.parameter_filter"] = [ /\Apin\z/i, /\Apin_/i ]
22
+ # => replaces the value for the exact (case-insensitive) key 'pin' and all
23
+ # (case-insensitive) keys beginning with 'pin_', with "[FILTERED]"
24
+ # Does not match keys with 'pin' as a substring, such as 'shipping_id'.
25
+ #
21
26
  # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
22
27
  # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
23
28
  # change { file: { code: "xxxx"} }
24
29
  #
25
30
  # env["action_dispatch.parameter_filter"] = -> (k, v) do
26
- # v.reverse! if k =~ /secret/i
31
+ # v.reverse! if k.match?(/secret/i)
27
32
  # end
28
33
  # => reverses the value to all keys matching /secret/i
29
34
  module FilterParameters
30
35
  ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
31
- NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
32
- NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
36
+ NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
37
+ NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
33
38
 
34
39
  def initialize
35
40
  super
@@ -41,6 +46,8 @@ module ActionDispatch
41
46
  # Returns a hash of parameters with all sensitive data replaced.
42
47
  def filtered_parameters
43
48
  @filtered_parameters ||= parameter_filter.filter(parameters)
49
+ rescue ActionDispatch::Http::Parameters::ParseError
50
+ @filtered_parameters = {}
44
51
  end
45
52
 
46
53
  # Returns a hash of request.env with all sensitive data replaced.
@@ -54,7 +61,6 @@ module ActionDispatch
54
61
  end
55
62
 
56
63
  private
57
-
58
64
  def parameter_filter # :doc:
59
65
  parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
60
66
  return NULL_PARAM_FILTER
@@ -69,7 +75,7 @@ module ActionDispatch
69
75
  end
70
76
 
71
77
  def parameter_filter_for(filters) # :doc:
72
- ParameterFilter.new(filters)
78
+ ActiveSupport::ParameterFilter.new(filters)
73
79
  end
74
80
 
75
81
  KV_RE = "[^&;=]+"
@@ -3,7 +3,7 @@
3
3
  module ActionDispatch
4
4
  module Http
5
5
  module FilterRedirect
6
- FILTERED = "[FILTERED]".freeze # :nodoc:
6
+ FILTERED = "[FILTERED]" # :nodoc:
7
7
 
8
8
  def filtered_location # :nodoc:
9
9
  if location_filter_match?
@@ -14,7 +14,6 @@ module ActionDispatch
14
14
  end
15
15
 
16
16
  private
17
-
18
17
  def location_filters
19
18
  if request
20
19
  request.get_header("action_dispatch.redirect_filter") || []
@@ -28,7 +27,7 @@ module ActionDispatch
28
27
  if String === filter
29
28
  location.include?(filter)
30
29
  elsif Regexp === filter
31
- location =~ filter
30
+ location.match?(filter)
32
31
  end
33
32
  end
34
33
  end
@@ -116,14 +116,14 @@ module ActionDispatch
116
116
  def env; @req.env.dup; end
117
117
 
118
118
  private
119
-
120
119
  # Converts an HTTP header name to an environment variable name if it is
121
120
  # not contained within the headers hash.
122
121
  def env_name(key)
123
122
  key = key.to_s
124
- if key =~ HTTP_HEADER
125
- key = key.upcase.tr("-", "_")
126
- key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
123
+ if HTTP_HEADER.match?(key)
124
+ key = key.upcase
125
+ key.tr!("-", "_")
126
+ key.prepend("HTTP_") unless CGI_VARIABLES.include?(key)
127
127
  end
128
128
  key
129
129
  end
@@ -7,24 +7,43 @@ module ActionDispatch
7
7
  module MimeNegotiation
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ class InvalidType < ::Mime::Type::InvalidMimeType; end
11
+
12
+ RESCUABLE_MIME_FORMAT_ERRORS = [
13
+ ActionController::BadRequest,
14
+ ActionDispatch::Http::Parameters::ParseError,
15
+ ]
16
+
10
17
  included do
11
18
  mattr_accessor :ignore_accept_header, default: false
19
+ cattr_accessor :return_only_media_type_on_content_type, default: false
12
20
  end
13
21
 
14
22
  # The MIME type of the HTTP request, such as Mime[:xml].
15
23
  def content_mime_type
16
24
  fetch_header("action_dispatch.request.content_type") do |k|
17
- v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/
25
+ v = if get_header("CONTENT_TYPE") =~ /^([^,;]*)/
18
26
  Mime::Type.lookup($1.strip.downcase)
19
27
  else
20
28
  nil
21
29
  end
22
30
  set_header k, v
31
+ rescue ::Mime::Type::InvalidMimeType => e
32
+ raise InvalidType, e.message
23
33
  end
24
34
  end
25
35
 
26
36
  def content_type
27
- content_mime_type && content_mime_type.to_s
37
+ if self.class.return_only_media_type_on_content_type
38
+ ActiveSupport::Deprecation.warn(
39
+ "Rails 7.1 will return Content-Type header without modification." \
40
+ " If you want just the MIME type, please use `#media_type` instead."
41
+ )
42
+
43
+ content_mime_type&.to_s
44
+ else
45
+ super
46
+ end
28
47
  end
29
48
 
30
49
  def has_content_type? # :nodoc:
@@ -42,6 +61,8 @@ module ActionDispatch
42
61
  Mime::Type.parse(header)
43
62
  end
44
63
  set_header k, v
64
+ rescue ::Mime::Type::InvalidMimeType => e
65
+ raise InvalidType, e.message
45
66
  end
46
67
  end
47
68
 
@@ -57,13 +78,7 @@ module ActionDispatch
57
78
 
58
79
  def formats
59
80
  fetch_header("action_dispatch.request.formats") do |k|
60
- params_readable = begin
61
- parameters[:format]
62
- rescue ActionController::BadRequest
63
- false
64
- end
65
-
66
- v = if params_readable
81
+ v = if params_readable?
67
82
  Array(Mime[parameters[:format]])
68
83
  elsif use_accept_header && valid_accept_header
69
84
  accepts
@@ -74,6 +89,11 @@ module ActionDispatch
74
89
  else
75
90
  [Mime[:html]]
76
91
  end
92
+
93
+ v = v.select do |format|
94
+ format.symbol || format.ref == "*/*"
95
+ end
96
+
77
97
  set_header k, v
78
98
  end
79
99
  end
@@ -82,13 +102,10 @@ module ActionDispatch
82
102
  def variant=(variant)
83
103
  variant = Array(variant)
84
104
 
85
- if variant.all? { |v| v.is_a?(Symbol) }
105
+ if variant.all?(Symbol)
86
106
  @variant = ActiveSupport::ArrayInquirer.new(variant)
87
107
  else
88
- raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols. " \
89
- "For security reasons, never directly set the variant to a user-provided value, " \
90
- "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
91
- "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
108
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols."
92
109
  end
93
110
  end
94
111
 
@@ -146,13 +163,24 @@ module ActionDispatch
146
163
  order.include?(Mime::ALL) ? format : nil
147
164
  end
148
165
 
149
- private
166
+ def should_apply_vary_header?
167
+ !params_readable? && use_accept_header && valid_accept_header
168
+ end
150
169
 
170
+ private
171
+ # We use normal content negotiation unless you include */* in your list,
172
+ # in which case we assume you're a browser and send HTML.
151
173
  BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
152
174
 
175
+ def params_readable? # :doc:
176
+ parameters[:format]
177
+ rescue *RESCUABLE_MIME_FORMAT_ERRORS
178
+ false
179
+ end
180
+
153
181
  def valid_accept_header # :doc:
154
182
  (xhr? && (accept.present? || content_mime_type)) ||
155
- (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
183
+ (accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
156
184
  end
157
185
 
158
186
  def use_accept_header # :doc: