actionpack 7.1.3 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +121 -465
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +2 -0
  5. data/lib/abstract_controller/base.rb +93 -100
  6. data/lib/abstract_controller/caching/fragments.rb +50 -53
  7. data/lib/abstract_controller/caching.rb +2 -0
  8. data/lib/abstract_controller/callbacks.rb +66 -64
  9. data/lib/abstract_controller/collector.rb +7 -7
  10. data/lib/abstract_controller/deprecator.rb +2 -0
  11. data/lib/abstract_controller/error.rb +2 -0
  12. data/lib/abstract_controller/helpers.rb +71 -84
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/rendering.rb +13 -12
  16. data/lib/abstract_controller/translation.rb +15 -7
  17. data/lib/abstract_controller/url_for.rb +8 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api/api_rendering.rb +2 -0
  20. data/lib/action_controller/api.rb +75 -72
  21. data/lib/action_controller/base.rb +198 -126
  22. data/lib/action_controller/caching.rb +15 -12
  23. data/lib/action_controller/deprecator.rb +2 -0
  24. data/lib/action_controller/form_builder.rb +20 -17
  25. data/lib/action_controller/log_subscriber.rb +3 -1
  26. data/lib/action_controller/metal/allow_browser.rb +123 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  28. data/lib/action_controller/metal/conditional_get.rb +187 -174
  29. data/lib/action_controller/metal/content_security_policy.rb +25 -24
  30. data/lib/action_controller/metal/cookies.rb +4 -2
  31. data/lib/action_controller/metal/data_streaming.rb +64 -55
  32. data/lib/action_controller/metal/default_headers.rb +5 -3
  33. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  34. data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
  35. data/lib/action_controller/metal/exceptions.rb +11 -9
  36. data/lib/action_controller/metal/flash.rb +12 -10
  37. data/lib/action_controller/metal/head.rb +12 -10
  38. data/lib/action_controller/metal/helpers.rb +63 -55
  39. data/lib/action_controller/metal/http_authentication.rb +211 -206
  40. data/lib/action_controller/metal/implicit_render.rb +17 -15
  41. data/lib/action_controller/metal/instrumentation.rb +15 -12
  42. data/lib/action_controller/metal/live.rb +117 -108
  43. data/lib/action_controller/metal/logging.rb +6 -4
  44. data/lib/action_controller/metal/mime_responds.rb +151 -142
  45. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  46. data/lib/action_controller/metal/params_wrapper.rb +57 -59
  47. data/lib/action_controller/metal/permissions_policy.rb +13 -12
  48. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  49. data/lib/action_controller/metal/redirecting.rb +112 -85
  50. data/lib/action_controller/metal/renderers.rb +50 -49
  51. data/lib/action_controller/metal/rendering.rb +104 -76
  52. data/lib/action_controller/metal/request_forgery_protection.rb +165 -134
  53. data/lib/action_controller/metal/rescue.rb +11 -9
  54. data/lib/action_controller/metal/streaming.rb +138 -136
  55. data/lib/action_controller/metal/strong_parameters.rb +525 -483
  56. data/lib/action_controller/metal/testing.rb +2 -0
  57. data/lib/action_controller/metal/url_for.rb +17 -15
  58. data/lib/action_controller/metal.rb +86 -60
  59. data/lib/action_controller/railtie.rb +3 -0
  60. data/lib/action_controller/railties/helpers.rb +2 -0
  61. data/lib/action_controller/renderer.rb +41 -36
  62. data/lib/action_controller/template_assertions.rb +4 -2
  63. data/lib/action_controller/test_case.rb +148 -129
  64. data/lib/action_controller.rb +10 -3
  65. data/lib/action_dispatch/constants.rb +8 -0
  66. data/lib/action_dispatch/deprecator.rb +2 -0
  67. data/lib/action_dispatch/http/cache.rb +27 -26
  68. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  69. data/lib/action_dispatch/http/content_security_policy.rb +61 -42
  70. data/lib/action_dispatch/http/filter_parameters.rb +18 -9
  71. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  72. data/lib/action_dispatch/http/headers.rb +22 -22
  73. data/lib/action_dispatch/http/mime_negotiation.rb +35 -41
  74. data/lib/action_dispatch/http/mime_type.rb +31 -24
  75. data/lib/action_dispatch/http/mime_types.rb +2 -0
  76. data/lib/action_dispatch/http/parameters.rb +11 -9
  77. data/lib/action_dispatch/http/permissions_policy.rb +20 -44
  78. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  79. data/lib/action_dispatch/http/request.rb +96 -76
  80. data/lib/action_dispatch/http/response.rb +88 -62
  81. data/lib/action_dispatch/http/upload.rb +18 -16
  82. data/lib/action_dispatch/http/url.rb +77 -75
  83. data/lib/action_dispatch/journey/formatter.rb +21 -9
  84. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  85. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
  86. data/lib/action_dispatch/journey/gtg/transition_table.rb +14 -12
  87. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  88. data/lib/action_dispatch/journey/nodes/node.rb +6 -5
  89. data/lib/action_dispatch/journey/parser.rb +4 -3
  90. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +4 -1
  92. data/lib/action_dispatch/journey/route.rb +9 -7
  93. data/lib/action_dispatch/journey/router/utils.rb +16 -15
  94. data/lib/action_dispatch/journey/router.rb +4 -2
  95. data/lib/action_dispatch/journey/routes.rb +4 -2
  96. data/lib/action_dispatch/journey/scanner.rb +4 -2
  97. data/lib/action_dispatch/journey/visitors.rb +2 -0
  98. data/lib/action_dispatch/journey.rb +2 -0
  99. data/lib/action_dispatch/log_subscriber.rb +2 -0
  100. data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
  101. data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
  102. data/lib/action_dispatch/middleware/callbacks.rb +3 -1
  103. data/lib/action_dispatch/middleware/cookies.rb +119 -104
  104. data/lib/action_dispatch/middleware/debug_exceptions.rb +16 -6
  105. data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
  106. data/lib/action_dispatch/middleware/debug_view.rb +2 -0
  107. data/lib/action_dispatch/middleware/exception_wrapper.rb +9 -14
  108. data/lib/action_dispatch/middleware/executor.rb +13 -2
  109. data/lib/action_dispatch/middleware/flash.rb +63 -51
  110. data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
  111. data/lib/action_dispatch/middleware/public_exceptions.rb +13 -7
  112. data/lib/action_dispatch/middleware/reloader.rb +5 -3
  113. data/lib/action_dispatch/middleware/remote_ip.rb +76 -72
  114. data/lib/action_dispatch/middleware/request_id.rb +14 -9
  115. data/lib/action_dispatch/middleware/server_timing.rb +4 -2
  116. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
  117. data/lib/action_dispatch/middleware/session/cache_store.rb +13 -8
  118. data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
  119. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
  120. data/lib/action_dispatch/middleware/show_exceptions.rb +31 -21
  121. data/lib/action_dispatch/middleware/ssl.rb +43 -40
  122. data/lib/action_dispatch/middleware/stack.rb +11 -10
  123. data/lib/action_dispatch/middleware/static.rb +33 -31
  124. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +1 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -1
  126. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
  127. data/lib/action_dispatch/railtie.rb +2 -4
  128. data/lib/action_dispatch/request/session.rb +23 -21
  129. data/lib/action_dispatch/request/utils.rb +2 -0
  130. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  131. data/lib/action_dispatch/routing/inspector.rb +6 -4
  132. data/lib/action_dispatch/routing/mapper.rb +673 -637
  133. data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
  134. data/lib/action_dispatch/routing/redirection.rb +37 -32
  135. data/lib/action_dispatch/routing/route_set.rb +60 -46
  136. data/lib/action_dispatch/routing/routes_proxy.rb +6 -4
  137. data/lib/action_dispatch/routing/url_for.rb +130 -125
  138. data/lib/action_dispatch/routing.rb +150 -148
  139. data/lib/action_dispatch/system_test_case.rb +91 -81
  140. data/lib/action_dispatch/system_testing/browser.rb +10 -3
  141. data/lib/action_dispatch/system_testing/driver.rb +3 -1
  142. data/lib/action_dispatch/system_testing/server.rb +2 -0
  143. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +32 -21
  144. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  145. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  146. data/lib/action_dispatch/testing/assertions/response.rb +26 -23
  147. data/lib/action_dispatch/testing/assertions/routing.rb +153 -84
  148. data/lib/action_dispatch/testing/assertions.rb +2 -0
  149. data/lib/action_dispatch/testing/integration.rb +222 -222
  150. data/lib/action_dispatch/testing/request_encoder.rb +11 -9
  151. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  152. data/lib/action_dispatch/testing/test_process.rb +11 -8
  153. data/lib/action_dispatch/testing/test_request.rb +3 -1
  154. data/lib/action_dispatch/testing/test_response.rb +27 -26
  155. data/lib/action_dispatch.rb +25 -27
  156. data/lib/action_pack/gem_version.rb +4 -2
  157. data/lib/action_pack/version.rb +3 -1
  158. data/lib/action_pack.rb +17 -16
  159. metadata +50 -16
@@ -1,29 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/object/deep_dup"
4
6
  require "active_support/core_ext/array/wrap"
5
7
 
6
8
  module ActionDispatch # :nodoc:
7
- # = Action Dispatch Content Security Policy
9
+ # # Action Dispatch Content Security Policy
8
10
  #
9
- # Configures the HTTP
10
- # {Content-Security-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy]
11
- # response header to help protect against XSS and injection attacks.
11
+ # Configures the HTTP [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
12
+ # response header to help protect against XSS and
13
+ # injection attacks.
12
14
  #
13
15
  # Example global policy:
14
16
  #
15
- # Rails.application.config.content_security_policy do |policy|
16
- # policy.default_src :self, :https
17
- # policy.font_src :self, :https, :data
18
- # policy.img_src :self, :https, :data
19
- # policy.object_src :none
20
- # policy.script_src :self, :https
21
- # policy.style_src :self, :https
17
+ # Rails.application.config.content_security_policy do |policy|
18
+ # policy.default_src :self, :https
19
+ # policy.font_src :self, :https, :data
20
+ # policy.img_src :self, :https, :data
21
+ # policy.object_src :none
22
+ # policy.script_src :self, :https
23
+ # policy.style_src :self, :https
22
24
  #
23
- # # Specify URI for violation reports
24
- # policy.report_uri "/csp-violation-report-endpoint"
25
- # end
25
+ # # Specify URI for violation reports
26
+ # policy.report_uri "/csp-violation-report-endpoint"
27
+ # end
26
28
  class ContentSecurityPolicy
29
+ class InvalidDirectiveError < StandardError
30
+ end
31
+
27
32
  class Middleware
28
33
  def initialize(app)
29
34
  @app = app
@@ -32,8 +37,8 @@ module ActionDispatch # :nodoc:
32
37
  def call(env)
33
38
  status, headers, _ = response = @app.call(env)
34
39
 
35
- # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
36
- # CSP headers might not match nonces in the cached HTML.
40
+ # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the
41
+ # new CSP headers might not match nonces in the cached HTML.
37
42
  return response if status == 304
38
43
 
39
44
  return response if policy_present?(headers)
@@ -190,14 +195,14 @@ module ActionDispatch # :nodoc:
190
195
  end
191
196
  end
192
197
 
193
- # Specify whether to prevent the user agent from loading any assets over
194
- # HTTP when the page uses HTTPS:
198
+ # Specify whether to prevent the user agent from loading any assets over HTTP
199
+ # when the page uses HTTPS:
195
200
  #
196
- # policy.block_all_mixed_content
201
+ # policy.block_all_mixed_content
197
202
  #
198
- # Pass +false+ to allow it again:
203
+ # Pass `false` to allow it again:
199
204
  #
200
- # policy.block_all_mixed_content false
205
+ # policy.block_all_mixed_content false
201
206
  #
202
207
  def block_all_mixed_content(enabled = true)
203
208
  if enabled
@@ -209,11 +214,11 @@ module ActionDispatch # :nodoc:
209
214
 
210
215
  # Restricts the set of plugins that can be embedded:
211
216
  #
212
- # policy.plugin_types "application/x-shockwave-flash"
217
+ # policy.plugin_types "application/x-shockwave-flash"
213
218
  #
214
219
  # Leave empty to allow all plugins:
215
220
  #
216
- # policy.plugin_types
221
+ # policy.plugin_types
217
222
  #
218
223
  def plugin_types(*types)
219
224
  if types.first
@@ -223,23 +228,23 @@ module ActionDispatch # :nodoc:
223
228
  end
224
229
  end
225
230
 
226
- # Enable the {report-uri}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri]
227
- # directive. Violation reports will be sent to the specified URI:
231
+ # Enable the [report-uri](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
232
+ # directive. Violation reports will be sent to the
233
+ # specified URI:
228
234
  #
229
- # policy.report_uri "/csp-violation-report-endpoint"
235
+ # policy.report_uri "/csp-violation-report-endpoint"
230
236
  #
231
237
  def report_uri(uri)
232
238
  @directives["report-uri"] = [uri]
233
239
  end
234
240
 
235
- # Specify asset types for which {Subresource Integrity}[https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity]
236
- # is required:
241
+ # Specify asset types for which [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
237
242
  #
238
- # policy.require_sri_for :script, :style
243
+ # policy.require_sri_for :script, :style
239
244
  #
240
245
  # Leave empty to not require Subresource Integrity:
241
246
  #
242
- # policy.require_sri_for
247
+ # policy.require_sri_for
243
248
  #
244
249
  def require_sri_for(*types)
245
250
  if types.first
@@ -249,18 +254,18 @@ module ActionDispatch # :nodoc:
249
254
  end
250
255
  end
251
256
 
252
- # Specify whether a {sandbox}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox]
257
+ # Specify whether a [sandbox](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
253
258
  # should be enabled for the requested resource:
254
259
  #
255
- # policy.sandbox
260
+ # policy.sandbox
256
261
  #
257
262
  # Values can be passed as arguments:
258
263
  #
259
- # policy.sandbox "allow-scripts", "allow-modals"
264
+ # policy.sandbox "allow-scripts", "allow-modals"
260
265
  #
261
- # Pass +false+ to disable the sandbox:
266
+ # Pass `false` to disable the sandbox:
262
267
  #
263
- # policy.sandbox false
268
+ # policy.sandbox false
264
269
  #
265
270
  def sandbox(*values)
266
271
  if values.empty?
@@ -274,11 +279,11 @@ module ActionDispatch # :nodoc:
274
279
 
275
280
  # Specify whether user agents should treat any assets over HTTP as HTTPS:
276
281
  #
277
- # policy.upgrade_insecure_requests
282
+ # policy.upgrade_insecure_requests
278
283
  #
279
- # Pass +false+ to disable it:
284
+ # Pass `false` to disable it:
280
285
  #
281
- # policy.upgrade_insecure_requests false
286
+ # policy.upgrade_insecure_requests false
282
287
  #
283
288
  def upgrade_insecure_requests(enabled = true)
284
289
  if enabled
@@ -317,9 +322,9 @@ module ActionDispatch # :nodoc:
317
322
  @directives.map do |directive, sources|
318
323
  if sources.is_a?(Array)
319
324
  if nonce && nonce_directive?(directive, nonce_directives)
320
- "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
325
+ "#{directive} #{build_directive(directive, sources, context).join(' ')} 'nonce-#{nonce}'"
321
326
  else
322
- "#{directive} #{build_directive(sources, context).join(' ')}"
327
+ "#{directive} #{build_directive(directive, sources, context).join(' ')}"
323
328
  end
324
329
  elsif sources
325
330
  directive
@@ -329,8 +334,22 @@ module ActionDispatch # :nodoc:
329
334
  end
330
335
  end
331
336
 
332
- def build_directive(sources, context)
333
- sources.map { |source| resolve_source(source, context) }
337
+ def validate(directive, sources)
338
+ sources.flatten.each do |source|
339
+ if source.include?(";") || source != source.gsub(/[[:space:]]/, "")
340
+ raise InvalidDirectiveError, <<~MSG.squish
341
+ Invalid Content Security Policy #{directive}: "#{source}".
342
+ Directive values must not contain whitespace or semicolons.
343
+ Please use multiple arguments or other directive methods instead.
344
+ MSG
345
+ end
346
+ end
347
+ end
348
+
349
+ def build_directive(directive, sources, context)
350
+ resolved_sources = sources.map { |source| resolve_source(source, context) }
351
+
352
+ validate(directive, resolved_sources)
334
353
  end
335
354
 
336
355
  def resolve_source(source, context)
@@ -1,18 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/parameter_filter"
4
6
 
5
7
  module ActionDispatch
6
8
  module Http
7
- # = Action Dispatch HTTP Filter Parameters
9
+ # # Action Dispatch HTTP Filter Parameters
8
10
  #
9
11
  # Allows you to specify sensitive query string and POST parameters to filter
10
12
  # from the request log.
11
13
  #
12
- # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
13
- # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
14
+ # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
15
+ # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
14
16
  #
15
- # For more information about filter behavior, see ActiveSupport::ParameterFilter.
17
+ # For more information about filter behavior, see
18
+ # ActiveSupport::ParameterFilter.
16
19
  module FilterParameters
17
20
  ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
18
21
  NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
@@ -43,7 +46,8 @@ module ActionDispatch
43
46
  @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
44
47
  end
45
48
 
46
- # Returns the +ActiveSupport::ParameterFilter+ object used to filter in this request.
49
+ # Returns the `ActiveSupport::ParameterFilter` object used to filter in this
50
+ # request.
47
51
  def parameter_filter
48
52
  @parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
49
53
  parameter_filter_for get_header("action_dispatch.parameter_filter")
@@ -64,12 +68,17 @@ module ActionDispatch
64
68
  ActiveSupport::ParameterFilter.new(filters)
65
69
  end
66
70
 
67
- KV_RE = "[^&;=]+"
68
- PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
69
71
  def filtered_query_string # :doc:
70
- query_string.gsub(PAIR_RE) do |_|
71
- parameter_filter.filter($1 => $2).first.join("=")
72
+ parts = query_string.split(/([&;])/)
73
+ filtered_parts = parts.map do |part|
74
+ if part.include?("=")
75
+ key, value = part.split("=", 2)
76
+ parameter_filter.filter(key => value).first.join("=")
77
+ else
78
+ part
79
+ end
72
80
  end
81
+ filtered_parts.join("")
73
82
  end
74
83
  end
75
84
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
6
  module Http
5
7
  module FilterRedirect
@@ -9,7 +11,7 @@ module ActionDispatch
9
11
  if location_filter_match?
10
12
  FILTERED
11
13
  else
12
- location
14
+ parameter_filtered_location
13
15
  end
14
16
  end
15
17
 
@@ -31,6 +33,25 @@ module ActionDispatch
31
33
  end
32
34
  end
33
35
  end
36
+
37
+ def parameter_filtered_location
38
+ uri = URI.parse(location)
39
+ unless uri.query.nil? || uri.query.empty?
40
+ parts = uri.query.split(/([&;])/)
41
+ filtered_parts = parts.map do |part|
42
+ if part.include?("=")
43
+ key, value = part.split("=", 2)
44
+ request.parameter_filter.filter(key => value).first.join("=")
45
+ else
46
+ part
47
+ end
48
+ end
49
+ uri.query = filtered_parts.join("")
50
+ end
51
+ uri.to_s
52
+ rescue URI::Error
53
+ FILTERED
54
+ end
34
55
  end
35
56
  end
36
57
  end
@@ -1,28 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
6
  module Http
5
- # = Action Dispatch HTTP \Headers
7
+ # # Action Dispatch HTTP Headers
6
8
  #
7
9
  # Provides access to the request's HTTP headers from the environment.
8
10
  #
9
- # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
10
- # headers = ActionDispatch::Http::Headers.from_hash(env)
11
- # headers["Content-Type"] # => "text/plain"
12
- # headers["User-Agent"] # => "curl/7.43.0"
11
+ # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
12
+ # headers = ActionDispatch::Http::Headers.from_hash(env)
13
+ # headers["Content-Type"] # => "text/plain"
14
+ # headers["User-Agent"] # => "curl/7.43.0"
13
15
  #
14
16
  # Also note that when headers are mapped to CGI-like variables by the Rack
15
17
  # server, both dashes and underscores are converted to underscores. This
16
18
  # ambiguity cannot be resolved at this stage anymore. Both underscores and
17
19
  # dashes have to be interpreted as if they were originally sent as dashes.
18
20
  #
19
- # # GET / HTTP/1.1
20
- # # ...
21
- # # User-Agent: curl/7.43.0
22
- # # X_Custom_Header: token
21
+ # # GET / HTTP/1.1
22
+ # # ...
23
+ # # User-Agent: curl/7.43.0
24
+ # # X_Custom_Header: token
23
25
  #
24
- # headers["X_Custom_Header"] # => nil
25
- # headers["X-Custom-Header"] # => "token"
26
+ # headers["X_Custom_Header"] # => nil
27
+ # headers["X-Custom-Header"] # => "token"
26
28
  class Headers
27
29
  CGI_VARIABLES = Set.new(%W[
28
30
  AUTH_TYPE
@@ -67,7 +69,7 @@ module ActionDispatch
67
69
  @req.set_header env_name(key), value
68
70
  end
69
71
 
70
- # Add a value to a multivalued header like +Vary+ or +Accept-Encoding+.
72
+ # Add a value to a multivalued header like `Vary` or `Accept-Encoding`.
71
73
  def add(key, value)
72
74
  @req.add_header env_name(key), value
73
75
  end
@@ -81,11 +83,10 @@ module ActionDispatch
81
83
 
82
84
  # Returns the value for the given key mapped to @env.
83
85
  #
84
- # If the key is not found and an optional code block is not provided,
85
- # raises a <tt>KeyError</tt> exception.
86
+ # If the key is not found and an optional code block is not provided, raises a
87
+ # `KeyError` exception.
86
88
  #
87
- # If the code block is provided, then it will be run and
88
- # its result returned.
89
+ # If the code block is provided, then it will be run and its result returned.
89
90
  def fetch(key, default = DEFAULT)
90
91
  @req.fetch_header(env_name(key)) do
91
92
  return default unless default == DEFAULT
@@ -99,16 +100,15 @@ module ActionDispatch
99
100
  end
100
101
 
101
102
  # Returns a new Http::Headers instance containing the contents of
102
- # <tt>headers_or_env</tt> and the original instance.
103
+ # `headers_or_env` and the original instance.
103
104
  def merge(headers_or_env)
104
105
  headers = @req.dup.headers
105
106
  headers.merge!(headers_or_env)
106
107
  headers
107
108
  end
108
109
 
109
- # Adds the contents of <tt>headers_or_env</tt> to original instance
110
- # entries; duplicate keys are overwritten with the values from
111
- # <tt>headers_or_env</tt>.
110
+ # Adds the contents of `headers_or_env` to original instance entries; duplicate
111
+ # keys are overwritten with the values from `headers_or_env`.
112
112
  def merge!(headers_or_env)
113
113
  headers_or_env.each do |key, value|
114
114
  @req.set_header env_name(key), value
@@ -118,8 +118,8 @@ module ActionDispatch
118
118
  def env; @req.env.dup; end
119
119
 
120
120
  private
121
- # Converts an HTTP header name to an environment variable name if it is
122
- # not contained within the headers hash.
121
+ # Converts an HTTP header name to an environment variable name if it is not
122
+ # contained within the headers hash.
123
123
  def env_name(key)
124
124
  key = key.to_s
125
125
  if HTTP_HEADER.match?(key)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/module/attribute_accessors"
4
6
 
5
7
  module ActionDispatch
@@ -16,23 +18,9 @@ module ActionDispatch
16
18
 
17
19
  included do
18
20
  mattr_accessor :ignore_accept_header, default: false
19
-
20
- def return_only_media_type_on_content_type=(value)
21
- ActionDispatch.deprecator.warn(
22
- "`config.action_dispatch.return_only_request_media_type_on_content_type` is deprecated and will" \
23
- " be removed in Rails 7.2."
24
- )
25
- end
26
-
27
- def return_only_media_type_on_content_type
28
- ActionDispatch.deprecator.warn(
29
- "`config.action_dispatch.return_only_request_media_type_on_content_type` is deprecated and will" \
30
- " be removed in Rails 7.2."
31
- )
32
- end
33
21
  end
34
22
 
35
- # The MIME type of the HTTP request, such as Mime[:xml].
23
+ # The MIME type of the HTTP request, such as [Mime](:xml).
36
24
  def content_mime_type
37
25
  fetch_header("action_dispatch.request.content_type") do |k|
38
26
  v = if get_header("CONTENT_TYPE") =~ /^([^,;]*)/
@@ -66,11 +54,16 @@ module ActionDispatch
66
54
  end
67
55
  end
68
56
 
69
- # Returns the MIME type for the \format used in the request.
57
+ # Returns the MIME type for the format used in the request.
58
+ #
59
+ # # GET /posts/5.xml
60
+ # request.format # => Mime[:xml]
61
+ #
62
+ # # GET /posts/5.xhtml
63
+ # request.format # => Mime[:html]
70
64
  #
71
- # GET /posts/5.xml | request.format => Mime[:xml]
72
- # GET /posts/5.xhtml | request.format => Mime[:html]
73
- # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
65
+ # # GET /posts/5
66
+ # request.format # => Mime[:html] or Mime[:js], or request.accepts.first
74
67
  #
75
68
  def format(_view_path = nil)
76
69
  formats.first || Mime::NullType.instance
@@ -98,7 +91,7 @@ module ActionDispatch
98
91
  end
99
92
  end
100
93
 
101
- # Sets the \variant for template.
94
+ # Sets the variant for template.
102
95
  def variant=(variant)
103
96
  variant = Array(variant)
104
97
 
@@ -113,36 +106,37 @@ module ActionDispatch
113
106
  @variant ||= ActiveSupport::ArrayInquirer.new
114
107
  end
115
108
 
116
- # Sets the \format by string extension, which can be used to force custom formats
109
+ # Sets the format by string extension, which can be used to force custom formats
117
110
  # that are not controlled by the extension.
118
111
  #
119
- # class ApplicationController < ActionController::Base
120
- # before_action :adjust_format_for_iphone
112
+ # class ApplicationController < ActionController::Base
113
+ # before_action :adjust_format_for_iphone
121
114
  #
122
- # private
123
- # def adjust_format_for_iphone
124
- # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
125
- # end
126
- # end
115
+ # private
116
+ # def adjust_format_for_iphone
117
+ # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
118
+ # end
119
+ # end
127
120
  def format=(extension)
128
121
  parameters[:format] = extension.to_s
129
122
  set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
130
123
  end
131
124
 
132
- # Sets the \formats by string extensions. This differs from #format= by allowing you
133
- # to set multiple, ordered formats, which is useful when you want to have a fallback.
125
+ # Sets the formats by string extensions. This differs from #format= by allowing
126
+ # you to set multiple, ordered formats, which is useful when you want to have a
127
+ # fallback.
134
128
  #
135
- # In this example, the +:iphone+ format will be used if it's available, otherwise it'll fall back
136
- # to the +:html+ format.
129
+ # In this example, the `:iphone` format will be used if it's available,
130
+ # otherwise it'll fall back to the `:html` format.
137
131
  #
138
- # class ApplicationController < ActionController::Base
139
- # before_action :adjust_format_for_iphone_with_html_fallback
132
+ # class ApplicationController < ActionController::Base
133
+ # before_action :adjust_format_for_iphone_with_html_fallback
140
134
  #
141
- # private
142
- # def adjust_format_for_iphone_with_html_fallback
143
- # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
144
- # end
145
- # end
135
+ # private
136
+ # def adjust_format_for_iphone_with_html_fallback
137
+ # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
138
+ # end
139
+ # end
146
140
  def formats=(extensions)
147
141
  parameters[:format] = extensions.first.to_s
148
142
  set_header "action_dispatch.request.formats", extensions.collect { |extension|
@@ -168,8 +162,8 @@ module ActionDispatch
168
162
  end
169
163
 
170
164
  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.
165
+ # We use normal content negotiation unless you include **/** in your list, in
166
+ # which case we assume you're a browser and send HTML.
173
167
  BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
174
168
 
175
169
  def params_readable?
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "singleton"
4
6
 
5
7
  module Mime
@@ -65,19 +67,20 @@ module Mime
65
67
  end
66
68
  end
67
69
 
68
- # Encapsulates the notion of a MIME type. Can be used at render time, for example, with:
70
+ # Encapsulates the notion of a MIME type. Can be used at render time, for
71
+ # example, with:
69
72
  #
70
- # class PostsController < ActionController::Base
71
- # def show
72
- # @post = Post.find(params[:id])
73
+ # class PostsController < ActionController::Base
74
+ # def show
75
+ # @post = Post.find(params[:id])
73
76
  #
74
- # respond_to do |format|
75
- # format.html
76
- # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
77
- # format.xml { render xml: @post }
77
+ # respond_to do |format|
78
+ # format.html
79
+ # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
80
+ # format.xml { render xml: @post }
81
+ # end
78
82
  # end
79
83
  # end
80
- # end
81
84
  class Type
82
85
  attr_reader :symbol
83
86
 
@@ -154,7 +157,7 @@ module Mime
154
157
  TRAILING_STAR_REGEXP = /^(text|application)\/\*/
155
158
  # all media-type parameters need to be before the q-parameter
156
159
  # https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2
157
- PARAMETER_SEPARATOR_REGEXP = /\s*;\s*q="?/
160
+ PARAMETER_SEPARATOR_REGEXP = /;\s*q="?/
158
161
  ACCEPT_HEADER_REGEXP = /[^,\s"](?:[^,"]|"[^"]*")*/
159
162
 
160
163
  def register_callback(&block)
@@ -162,16 +165,20 @@ module Mime
162
165
  end
163
166
 
164
167
  def lookup(string)
168
+ return LOOKUP[string] if LOOKUP.key?(string)
169
+
165
170
  # fallback to the media-type without parameters if it was not found
166
- LOOKUP[string] || LOOKUP[string.split(";", 2)[0]&.rstrip] || Type.new(string)
171
+ string = string.split(";", 2)[0]&.rstrip
172
+ LOOKUP[string] || Type.new(string)
167
173
  end
168
174
 
169
175
  def lookup_by_extension(extension)
170
176
  EXTENSION_LOOKUP[extension.to_s]
171
177
  end
172
178
 
173
- # Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for
174
- # rendering different HTML versions depending on the user agent, like an iPhone.
179
+ # Registers an alias that's not used on MIME type lookup, but can be referenced
180
+ # directly. Especially useful for rendering different HTML versions depending on
181
+ # the user agent, like an iPhone.
175
182
  def register_alias(string, symbol, extension_synonyms = [])
176
183
  register(string, symbol, [], extension_synonyms, true)
177
184
  end
@@ -193,7 +200,7 @@ module Mime
193
200
  def parse(accept_header)
194
201
  if !accept_header.include?(",")
195
202
  if (index = accept_header.index(PARAMETER_SEPARATOR_REGEXP))
196
- accept_header = accept_header[0, index]
203
+ accept_header = accept_header[0, index].strip
197
204
  end
198
205
  return [] if accept_header.blank?
199
206
  parse_trailing_star(accept_header) || Array(Mime::Type.lookup(accept_header))
@@ -221,11 +228,11 @@ module Mime
221
228
  parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
222
229
  end
223
230
 
224
- # For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
225
- # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
231
+ # For an input of `'text'`, returns `[Mime[:json], Mime[:xml], Mime[:ics],
232
+ # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]`.
226
233
  #
227
- # For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
228
- # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
234
+ # For an input of `'application'`, returns `[Mime[:html], Mime[:js], Mime[:xml],
235
+ # Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]`.
229
236
  def parse_data_with_trailing_star(type)
230
237
  Mime::SET.select { |m| m.match?(type) }
231
238
  end
@@ -234,7 +241,7 @@ module Mime
234
241
  #
235
242
  # To unregister a MIME type:
236
243
  #
237
- # Mime::Type.unregister(:mobile)
244
+ # Mime::Type.unregister(:mobile)
238
245
  def unregister(symbol)
239
246
  symbol = symbol.downcase
240
247
  if mime = Mime[symbol]
@@ -326,7 +333,7 @@ module Mime
326
333
  def to_ary; end
327
334
  def to_a; end
328
335
 
329
- def method_missing(method, *args)
336
+ def method_missing(method, ...)
330
337
  if method.end_with?("?")
331
338
  method[0..-2].downcase.to_sym == to_sym
332
339
  else
@@ -350,9 +357,9 @@ module Mime
350
357
  def html?; true; end
351
358
  end
352
359
 
353
- # ALL isn't a real MIME type, so we don't register it for lookup with the
354
- # other concrete types. It's a wildcard match that we use for +respond_to+
355
- # negotiation internals.
360
+ # ALL isn't a real MIME type, so we don't register it for lookup with the other
361
+ # concrete types. It's a wildcard match that we use for `respond_to` negotiation
362
+ # internals.
356
363
  ALL = AllType.instance
357
364
 
358
365
  class NullType
@@ -373,7 +380,7 @@ module Mime
373
380
  method.end_with?("?")
374
381
  end
375
382
 
376
- def method_missing(method, *args)
383
+ def method_missing(method, ...)
377
384
  false if method.end_with?("?")
378
385
  end
379
386
  end
@@ -3,6 +3,8 @@
3
3
  # Build list of Mime types for HTTP responses
4
4
  # https://www.iana.org/assignments/media-types/
5
5
 
6
+ # :markup: markdown
7
+
6
8
  Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
7
9
  Mime::Type.register "text/plain", :text, [], %w(txt)
8
10
  Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )