actionpack 7.0.4 → 7.1.5.1

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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +495 -257
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/abstract_controller/base.rb +20 -11
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +75 -28
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +12 -14
  12. data/lib/abstract_controller/translation.rb +11 -6
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +6 -4
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/basic_implicit_render.rb +3 -1
  22. data/lib/action_controller/metal/conditional_get.rb +121 -123
  23. data/lib/action_controller/metal/content_security_policy.rb +5 -5
  24. data/lib/action_controller/metal/data_streaming.rb +20 -18
  25. data/lib/action_controller/metal/default_headers.rb +2 -0
  26. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  28. data/lib/action_controller/metal/exceptions.rb +8 -0
  29. data/lib/action_controller/metal/head.rb +9 -7
  30. data/lib/action_controller/metal/helpers.rb +3 -14
  31. data/lib/action_controller/metal/http_authentication.rb +15 -9
  32. data/lib/action_controller/metal/implicit_render.rb +5 -3
  33. data/lib/action_controller/metal/instrumentation.rb +8 -1
  34. data/lib/action_controller/metal/live.rb +25 -1
  35. data/lib/action_controller/metal/mime_responds.rb +2 -2
  36. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  37. data/lib/action_controller/metal/permissions_policy.rb +2 -2
  38. data/lib/action_controller/metal/redirecting.rb +29 -8
  39. data/lib/action_controller/metal/renderers.rb +4 -4
  40. data/lib/action_controller/metal/rendering.rb +114 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
  42. data/lib/action_controller/metal/rescue.rb +6 -3
  43. data/lib/action_controller/metal/streaming.rb +71 -31
  44. data/lib/action_controller/metal/strong_parameters.rb +200 -103
  45. data/lib/action_controller/metal/url_for.rb +9 -4
  46. data/lib/action_controller/metal.rb +79 -21
  47. data/lib/action_controller/railtie.rb +24 -10
  48. data/lib/action_controller/renderer.rb +99 -85
  49. data/lib/action_controller/test_case.rb +18 -8
  50. data/lib/action_controller.rb +13 -3
  51. data/lib/action_dispatch/constants.rb +32 -0
  52. data/lib/action_dispatch/deprecator.rb +7 -0
  53. data/lib/action_dispatch/http/cache.rb +9 -11
  54. data/lib/action_dispatch/http/content_security_policy.rb +35 -13
  55. data/lib/action_dispatch/http/filter_parameters.rb +23 -32
  56. data/lib/action_dispatch/http/headers.rb +3 -1
  57. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  58. data/lib/action_dispatch/http/mime_type.rb +37 -11
  59. data/lib/action_dispatch/http/mime_types.rb +3 -1
  60. data/lib/action_dispatch/http/parameters.rb +1 -1
  61. data/lib/action_dispatch/http/permissions_policy.rb +38 -23
  62. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  63. data/lib/action_dispatch/http/request.rb +85 -32
  64. data/lib/action_dispatch/http/response.rb +80 -63
  65. data/lib/action_dispatch/http/upload.rb +15 -2
  66. data/lib/action_dispatch/journey/formatter.rb +8 -2
  67. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  68. data/lib/action_dispatch/journey/route.rb +3 -2
  69. data/lib/action_dispatch/journey/router.rb +9 -8
  70. data/lib/action_dispatch/journey/routes.rb +2 -2
  71. data/lib/action_dispatch/log_subscriber.rb +23 -0
  72. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  73. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  74. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  75. data/lib/action_dispatch/middleware/cookies.rb +108 -117
  76. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  77. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  78. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  80. data/lib/action_dispatch/middleware/executor.rb +7 -1
  81. data/lib/action_dispatch/middleware/flash.rb +7 -0
  82. data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  85. data/lib/action_dispatch/middleware/remote_ip.rb +21 -20
  86. data/lib/action_dispatch/middleware/request_id.rb +4 -2
  87. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  88. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  89. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  90. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  91. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  92. data/lib/action_dispatch/middleware/show_exceptions.rb +39 -22
  93. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  94. data/lib/action_dispatch/middleware/stack.rb +7 -2
  95. data/lib/action_dispatch/middleware/static.rb +14 -10
  96. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  97. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  98. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  99. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  102. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  103. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  105. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  107. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  108. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  109. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  110. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  111. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -41
  112. data/lib/action_dispatch/railtie.rb +13 -4
  113. data/lib/action_dispatch/request/session.rb +16 -6
  114. data/lib/action_dispatch/request/utils.rb +8 -3
  115. data/lib/action_dispatch/routing/inspector.rb +54 -6
  116. data/lib/action_dispatch/routing/mapper.rb +97 -26
  117. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  118. data/lib/action_dispatch/routing/redirection.rb +15 -6
  119. data/lib/action_dispatch/routing/route_set.rb +53 -23
  120. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  121. data/lib/action_dispatch/routing/url_for.rb +26 -22
  122. data/lib/action_dispatch/routing.rb +7 -7
  123. data/lib/action_dispatch/system_test_case.rb +3 -3
  124. data/lib/action_dispatch/system_testing/browser.rb +25 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +15 -23
  126. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  127. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  128. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  129. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  130. data/lib/action_dispatch/testing/assertions.rb +3 -1
  131. data/lib/action_dispatch/testing/integration.rb +27 -17
  132. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  133. data/lib/action_dispatch/testing/test_process.rb +4 -3
  134. data/lib/action_dispatch/testing/test_request.rb +1 -1
  135. data/lib/action_dispatch/testing/test_response.rb +23 -9
  136. data/lib/action_dispatch.rb +41 -4
  137. data/lib/action_pack/gem_version.rb +4 -4
  138. data/lib/action_pack/version.rb +1 -1
  139. data/lib/action_pack.rb +1 -1
  140. metadata +68 -32
@@ -3,6 +3,8 @@
3
3
  require "active_support/core_ext/object/deep_dup"
4
4
 
5
5
  module ActionDispatch # :nodoc:
6
+ # = Action Dispatch \PermissionsPolicy
7
+ #
6
8
  # Configures the HTTP
7
9
  # {Feature-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy]
8
10
  # response header to specify which browser features the current document and
@@ -19,47 +21,38 @@ module ActionDispatch # :nodoc:
19
21
  # policy.payment :self, "https://secure.example.com"
20
22
  # end
21
23
  #
24
+ # The Feature-Policy header has been renamed to Permissions-Policy.
25
+ # The Permissions-Policy requires a different implementation and isn't
26
+ # yet supported by all browsers. To avoid having to rename this
27
+ # middleware in the future we use the new name for the middleware but
28
+ # keep the old header name and implementation for now.
22
29
  class PermissionsPolicy
23
30
  class Middleware
24
- CONTENT_TYPE = "Content-Type"
25
- # The Feature-Policy header has been renamed to Permissions-Policy.
26
- # The Permissions-Policy requires a different implementation and isn't
27
- # yet supported by all browsers. To avoid having to rename this
28
- # middleware in the future we use the new name for the middleware but
29
- # keep the old header name and implementation for now.
30
- POLICY = "Feature-Policy"
31
-
32
31
  def initialize(app)
33
32
  @app = app
34
33
  end
35
34
 
36
35
  def call(env)
37
- request = ActionDispatch::Request.new(env)
38
36
  _, headers, _ = response = @app.call(env)
39
37
 
40
- return response unless html_response?(headers)
41
38
  return response if policy_present?(headers)
42
39
 
40
+ request = ActionDispatch::Request.new(env)
41
+
43
42
  if policy = request.permissions_policy
44
- headers[POLICY] = policy.build(request.controller_instance)
43
+ headers[ActionDispatch::Constants::FEATURE_POLICY] = policy.build(request.controller_instance)
45
44
  end
46
45
 
47
46
  if policy_empty?(policy)
48
- headers.delete(POLICY)
47
+ headers.delete(ActionDispatch::Constants::FEATURE_POLICY)
49
48
  end
50
49
 
51
50
  response
52
51
  end
53
52
 
54
53
  private
55
- def html_response?(headers)
56
- if content_type = headers[CONTENT_TYPE]
57
- /html/.match?(content_type)
58
- end
59
- end
60
-
61
54
  def policy_present?(headers)
62
- headers[POLICY]
55
+ headers[ActionDispatch::Constants::FEATURE_POLICY]
63
56
  end
64
57
 
65
58
  def policy_empty?(policy)
@@ -85,7 +78,7 @@ module ActionDispatch # :nodoc:
85
78
  }.freeze
86
79
 
87
80
  # List of available permissions can be found at
88
- # https://github.com/w3c/webappsec-permissions-policy/blob/master/features.md#policy-controlled-features
81
+ # https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md#policy-controlled-features
89
82
  DIRECTIVES = {
90
83
  accelerometer: "accelerometer",
91
84
  ambient_light_sensor: "ambient-light-sensor",
@@ -95,15 +88,18 @@ module ActionDispatch # :nodoc:
95
88
  fullscreen: "fullscreen",
96
89
  geolocation: "geolocation",
97
90
  gyroscope: "gyroscope",
91
+ hid: "hid",
92
+ idle_detection: "idle-detection",
98
93
  magnetometer: "magnetometer",
99
94
  microphone: "microphone",
100
95
  midi: "midi",
101
96
  payment: "payment",
102
97
  picture_in_picture: "picture-in-picture",
103
- speaker: "speaker",
98
+ screen_wake_lock: "screen-wake-lock",
99
+ serial: "serial",
100
+ sync_xhr: "sync-xhr",
104
101
  usb: "usb",
105
- vibrate: "vibrate",
106
- vr: "vr",
102
+ web_share: "web-share",
107
103
  }.freeze
108
104
 
109
105
  private_constant :MAPPINGS, :DIRECTIVES
@@ -129,6 +125,25 @@ module ActionDispatch # :nodoc:
129
125
  end
130
126
  end
131
127
 
128
+ %w[speaker vibrate vr].each do |directive|
129
+ define_method(directive) do |*sources|
130
+ ActionDispatch.deprecator.warn(<<~MSG)
131
+ The `#{directive}` permissions policy directive is deprecated
132
+ and will be removed in Rails 7.2.
133
+
134
+ There is no browser support for this directive, and no plan
135
+ for browser support in the future. You can just remove this
136
+ directive from your application.
137
+ MSG
138
+
139
+ if sources.first
140
+ @directives[directive] = apply_mappings(sources)
141
+ else
142
+ @directives.delete(directive)
143
+ end
144
+ end
145
+ end
146
+
132
147
  def build(context = nil)
133
148
  build_directives(context).compact.join("; ")
134
149
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :enddoc:
4
+
3
5
  require "rack/cache"
4
6
  require "rack/cache/context"
5
7
  require "active_support/cache"
@@ -72,7 +72,7 @@ module ActionDispatch
72
72
 
73
73
  PASS_NOT_FOUND = Class.new { # :nodoc:
74
74
  def self.action(_); self; end
75
- def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end
75
+ def self.call(_); [404, { Constants::X_CASCADE => "pass" }, []]; end
76
76
  def self.action_encoding_template(action); false; end
77
77
  }
78
78
 
@@ -107,22 +107,21 @@ module ActionDispatch
107
107
  has_header? key
108
108
  end
109
109
 
110
- # List of HTTP request methods from the following RFCs:
111
- # Hypertext Transfer Protocol -- HTTP/1.1 (https://www.ietf.org/rfc/rfc2616.txt)
112
- # HTTP Extensions for Distributed Authoring -- WEBDAV (https://www.ietf.org/rfc/rfc2518.txt)
113
- # Versioning Extensions to WebDAV (https://www.ietf.org/rfc/rfc3253.txt)
114
- # Ordered Collections Protocol (WebDAV) (https://www.ietf.org/rfc/rfc3648.txt)
115
- # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (https://www.ietf.org/rfc/rfc3744.txt)
116
- # Web Distributed Authoring and Versioning (WebDAV) SEARCH (https://www.ietf.org/rfc/rfc5323.txt)
117
- # Calendar Extensions to WebDAV (https://www.ietf.org/rfc/rfc4791.txt)
118
- # PATCH Method for HTTP (https://www.ietf.org/rfc/rfc5789.txt)
110
+ # HTTP methods from {RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1}[https://www.ietf.org/rfc/rfc2616.txt]
119
111
  RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
112
+ # HTTP methods from {RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV}[https://www.ietf.org/rfc/rfc2518.txt]
120
113
  RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
114
+ # HTTP methods from {RFC 3253: Versioning Extensions to WebDAV}[https://www.ietf.org/rfc/rfc3253.txt]
121
115
  RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
116
+ # HTTP methods from {RFC 3648: WebDAV Ordered Collections Protocol}[https://www.ietf.org/rfc/rfc3648.txt]
122
117
  RFC3648 = %w(ORDERPATCH)
118
+ # HTTP methods from {RFC 3744: WebDAV Access Control Protocol}[https://www.ietf.org/rfc/rfc3744.txt]
123
119
  RFC3744 = %w(ACL)
120
+ # HTTP methods from {RFC 5323: WebDAV SEARCH}[https://www.ietf.org/rfc/rfc5323.txt]
124
121
  RFC5323 = %w(SEARCH)
122
+ # HTTP methods from {RFC 4791: Calendaring Extensions to WebDAV}[https://www.ietf.org/rfc/rfc4791.txt]
125
123
  RFC4791 = %w(MKCALENDAR)
124
+ # HTTP methods from {RFC 5789: PATCH Method for HTTP}[https://www.ietf.org/rfc/rfc5789.txt]
126
125
  RFC5789 = %w(PATCH)
127
126
 
128
127
  HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789
@@ -146,6 +145,18 @@ module ActionDispatch
146
145
  @request_method ||= check_method(super)
147
146
  end
148
147
 
148
+ # Returns the URI pattern of the matched route for the request,
149
+ # using the same format as `bin/rails routes`:
150
+ #
151
+ # request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
152
+ def route_uri_pattern
153
+ get_header("action_dispatch.route_uri_pattern")
154
+ end
155
+
156
+ def route_uri_pattern=(pattern) # :nodoc:
157
+ set_header("action_dispatch.route_uri_pattern", pattern)
158
+ end
159
+
149
160
  def routes # :nodoc:
150
161
  get_header("action_dispatch.routes")
151
162
  end
@@ -180,13 +191,6 @@ module ActionDispatch
180
191
  get_header "action_dispatch.http_auth_salt"
181
192
  end
182
193
 
183
- def show_exceptions? # :nodoc:
184
- # We're treating `nil` as "unset", and we want the default setting to be
185
- # `true`. This logic should be extracted to `env_config` and calculated
186
- # once.
187
- !(get_header("action_dispatch.show_exceptions") == false)
188
- end
189
-
190
194
  # Returns a symbol form of the #request_method.
191
195
  def request_method_symbol
192
196
  HTTP_METHOD_LOOKUP[request_method]
@@ -195,9 +199,20 @@ module ActionDispatch
195
199
  # Returns the original value of the environment's REQUEST_METHOD,
196
200
  # even if it was overridden by middleware. See #request_method for
197
201
  # more information.
198
- def method
199
- @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header("REQUEST_METHOD"))
202
+ #
203
+ # For debugging purposes, when called with arguments this method will
204
+ # fall back to Object#method
205
+ def method(*args)
206
+ if args.empty?
207
+ @method ||= check_method(
208
+ get_header("rack.methodoverride.original_method") ||
209
+ get_header("REQUEST_METHOD")
210
+ )
211
+ else
212
+ super
213
+ end
200
214
  end
215
+ ruby2_keywords(:method)
201
216
 
202
217
  # Returns a symbol form of the #method.
203
218
  def method_symbol
@@ -214,11 +229,12 @@ module ActionDispatch
214
229
  # Early Hints is an HTTP/2 status code that indicates hints to help a client start
215
230
  # making preparations for processing the final response.
216
231
  #
217
- # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers.
232
+ # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for
233
+ # link headers.
218
234
  #
219
235
  # The +send_early_hints+ method accepts a hash of links as follows:
220
236
  #
221
- # send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
237
+ # send_early_hints("link" => "</style.css>; rel=preload; as=style,</script.js>; rel=preload")
222
238
  #
223
239
  # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the
224
240
  # Early Hints headers are included by default if supported.
@@ -268,10 +284,11 @@ module ActionDispatch
268
284
 
269
285
  # Returns the content length of the request as an integer.
270
286
  def content_length
287
+ return raw_post.bytesize if headers.key?("Transfer-Encoding")
271
288
  super.to_i
272
289
  end
273
290
 
274
- # Returns true if the "X-Requested-With" header contains "XMLHttpRequest"
291
+ # Returns true if the +X-Requested-With+ header contains "XMLHttpRequest"
275
292
  # (case-insensitive), which may need to be manually added depending on the
276
293
  # choice of JavaScript libraries and frameworks.
277
294
  def xml_http_request?
@@ -297,7 +314,7 @@ module ActionDispatch
297
314
 
298
315
  ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id" # :nodoc:
299
316
 
300
- # Returns the unique request id, which is based on either the X-Request-Id header that can
317
+ # Returns the unique request id, which is based on either the +X-Request-Id+ header that can
301
318
  # be generated by a firewall, load balancer, or web server, or by the RequestId middleware
302
319
  # (which sets the +action_dispatch.request_id+ environment variable).
303
320
  #
@@ -322,9 +339,7 @@ module ActionDispatch
322
339
  # work with raw requests directly.
323
340
  def raw_post
324
341
  unless has_header? "RAW_POST_DATA"
325
- raw_post_body = body
326
- set_header("RAW_POST_DATA", raw_post_body.read(content_length))
327
- raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
342
+ set_header("RAW_POST_DATA", read_body_stream)
328
343
  end
329
344
  get_header "RAW_POST_DATA"
330
345
  end
@@ -341,13 +356,13 @@ module ActionDispatch
341
356
  end
342
357
 
343
358
  # Determine whether the request body contains form-data by checking
344
- # the request Content-Type for one of the media-types:
345
- # "application/x-www-form-urlencoded" or "multipart/form-data". The
359
+ # the request +Content-Type+ for one of the media-types:
360
+ # +application/x-www-form-urlencoded+ or +multipart/form-data+. The
346
361
  # list of form-data media types can be modified through the
347
362
  # +FORM_DATA_MEDIA_TYPES+ array.
348
363
  #
349
364
  # A request body is not assumed to contain form-data when no
350
- # Content-Type header is provided and the request_method is POST.
365
+ # +Content-Type+ header is provided and the request_method is POST.
351
366
  def form_data?
352
367
  FORM_DATA_MEDIA_TYPES.include?(media_type)
353
368
  end
@@ -358,6 +373,7 @@ module ActionDispatch
358
373
 
359
374
  def reset_session
360
375
  session.destroy
376
+ reset_csrf_token
361
377
  end
362
378
 
363
379
  def session=(session) # :nodoc:
@@ -379,7 +395,7 @@ module ActionDispatch
379
395
  Request::Utils.check_param_encoding(rack_query_params)
380
396
  set_header k, Request::Utils.normalize_encode_params(rack_query_params)
381
397
  end
382
- rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
398
+ rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError, Rack::QueryParser::ParamsTooDeepError => e
383
399
  raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
384
400
  end
385
401
  alias :query_parameters :GET
@@ -394,7 +410,7 @@ module ActionDispatch
394
410
  Request::Utils.check_param_encoding(pr)
395
411
  self.request_parameters = Request::Utils.normalize_encode_params(pr)
396
412
  end
397
- rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
413
+ rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError, Rack::QueryParser::ParamsTooDeepError, EOFError => e
398
414
  raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
399
415
  end
400
416
  alias :request_parameters :POST
@@ -429,15 +445,52 @@ module ActionDispatch
429
445
  "#<#{self.class.name} #{method} #{original_url.dump} for #{remote_ip}>"
430
446
  end
431
447
 
448
+ def reset_csrf_token
449
+ controller_instance.reset_csrf_token(self) if controller_instance.respond_to?(:reset_csrf_token)
450
+ end
451
+
452
+ def commit_csrf_token
453
+ controller_instance.commit_csrf_token(self) if controller_instance.respond_to?(:commit_csrf_token)
454
+ end
455
+
432
456
  private
433
457
  def check_method(name)
434
- HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
458
+ if name
459
+ HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
460
+ end
461
+
435
462
  name
436
463
  end
437
464
 
438
465
  def default_session
439
466
  Session.disabled(self)
440
467
  end
468
+
469
+ def read_body_stream
470
+ if body_stream
471
+ reset_stream(body_stream) do
472
+ if headers.key?("Transfer-Encoding")
473
+ body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present
474
+ else
475
+ body_stream.read(content_length)
476
+ end
477
+ end
478
+ end
479
+ end
480
+
481
+ def reset_stream(body_stream)
482
+ if body_stream.respond_to?(:rewind)
483
+ body_stream.rewind
484
+
485
+ content = yield
486
+
487
+ body_stream.rewind
488
+
489
+ content
490
+ else
491
+ yield
492
+ end
493
+ end
441
494
  end
442
495
  end
443
496
 
@@ -6,23 +6,23 @@ require "action_dispatch/http/cache"
6
6
  require "monitor"
7
7
 
8
8
  module ActionDispatch # :nodoc:
9
+ # = Action Dispatch \Response
10
+ #
9
11
  # Represents an HTTP response generated by a controller action. Use it to
10
12
  # retrieve the current state of the response, or customize the response. It can
11
13
  # either represent a real HTTP response (i.e. one that is meant to be sent
12
14
  # back to the web browser) or a TestResponse (i.e. one that is generated
13
15
  # from integration tests).
14
16
  #
15
- # \Response is mostly a Ruby on \Rails framework implementation detail, and
16
- # should never be used directly in controllers. Controllers should use the
17
- # methods defined in ActionController::Base instead. For example, if you want
18
- # to set the HTTP response's content MIME type, then use
19
- # ActionControllerBase#headers instead of Response#headers.
17
+ # The \Response object for the current request is exposed on controllers as
18
+ # ActionController::Metal#response. ActionController::Metal also provides a
19
+ # few additional methods that delegate to attributes of the \Response such as
20
+ # ActionController::Metal#headers.
20
21
  #
21
- # Nevertheless, integration tests may want to inspect controller responses in
22
- # more detail, and that's when \Response can be useful for application
23
- # developers. Integration test methods such as
24
- # Integration::RequestHelpers#get and Integration::RequestHelpers#post return
25
- # objects of type TestResponse (which are of course also of type \Response).
22
+ # Integration tests will likely also want to inspect responses in
23
+ # more detail. Methods such as Integration::RequestHelpers#get
24
+ # and Integration::RequestHelpers#post return instances of
25
+ # TestResponse (which inherits from \Response) for this purpose.
26
26
  #
27
27
  # For example, the following demo integration test prints the body of the
28
28
  # controller response to the console:
@@ -34,41 +34,43 @@ module ActionDispatch # :nodoc:
34
34
  # end
35
35
  # end
36
36
  class Response
37
- class Header < DelegateClass(Hash) # :nodoc:
38
- def initialize(response, header)
39
- @response = response
40
- super(header)
41
- end
42
-
43
- def []=(k, v)
44
- if @response.sending? || @response.sent?
45
- raise ActionDispatch::IllegalStateError, "header already sent"
46
- end
47
-
48
- super
49
- end
50
-
51
- def merge(other)
52
- self.class.new @response, __getobj__.merge(other)
53
- end
54
-
55
- def to_hash
56
- __getobj__.dup
57
- end
37
+ begin
38
+ # For `Rack::Headers` (Rack 3+):
39
+ require "rack/headers"
40
+ Headers = ::Rack::Headers
41
+ rescue LoadError
42
+ # For `Rack::Utils::HeaderHash`:
43
+ require "rack/utils"
44
+ Headers = ::Rack::Utils::HeaderHash
58
45
  end
59
46
 
47
+ # To be deprecated:
48
+ Header = Headers
49
+
60
50
  # The request that the response is responding to.
61
51
  attr_accessor :request
62
52
 
63
53
  # The HTTP status code.
64
54
  attr_reader :status
65
55
 
66
- # Get headers for this response.
67
- attr_reader :header
56
+ # The headers for the response.
57
+ #
58
+ # header["Content-Type"] # => "text/plain"
59
+ # header["Content-Type"] = "application/json"
60
+ # header["Content-Type"] # => "application/json"
61
+ #
62
+ # Also aliased as +headers+.
63
+ #
64
+ # headers["Content-Type"] # => "text/plain"
65
+ # headers["Content-Type"] = "application/json"
66
+ # headers["Content-Type"] # => "application/json"
67
+ #
68
+ # Also aliased as +header+ for compatibility.
69
+ attr_reader :headers
68
70
 
69
- alias_method :headers, :header
71
+ alias_method :header, :headers
70
72
 
71
- delegate :[], :[]=, to: :@header
73
+ delegate :[], :[]=, to: :@headers
72
74
 
73
75
  def each(&block)
74
76
  sending!
@@ -79,7 +81,6 @@ module ActionDispatch # :nodoc:
79
81
 
80
82
  CONTENT_TYPE = "Content-Type"
81
83
  SET_COOKIE = "Set-Cookie"
82
- LOCATION = "Location"
83
84
  NO_CONTENT_CODES = [100, 101, 102, 103, 204, 205, 304]
84
85
 
85
86
  cattr_accessor :default_charset, default: "utf-8"
@@ -102,6 +103,12 @@ module ActionDispatch # :nodoc:
102
103
  @str_body = nil
103
104
  end
104
105
 
106
+ def to_ary
107
+ @buf.respond_to?(:to_ary) ?
108
+ @buf.to_ary :
109
+ @buf.each
110
+ end
111
+
105
112
  def body
106
113
  @str_body ||= begin
107
114
  buf = +""
@@ -117,6 +124,7 @@ module ActionDispatch # :nodoc:
117
124
  @response.commit!
118
125
  @buf.push string
119
126
  end
127
+ alias_method :<<, :write
120
128
 
121
129
  def each(&block)
122
130
  if @str_body
@@ -146,9 +154,9 @@ module ActionDispatch # :nodoc:
146
154
  end
147
155
  end
148
156
 
149
- def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
150
- header = merge_default_headers(header, default_headers)
151
- new status, header, body
157
+ def self.create(status = 200, headers = {}, body = [], default_headers: self.default_headers)
158
+ headers = merge_default_headers(headers, default_headers)
159
+ new status, headers, body
152
160
  end
153
161
 
154
162
  def self.merge_default_headers(original, default)
@@ -158,10 +166,14 @@ module ActionDispatch # :nodoc:
158
166
  # The underlying body, as a streamable object.
159
167
  attr_reader :stream
160
168
 
161
- def initialize(status = 200, header = {}, body = [])
169
+ def initialize(status = 200, headers = nil, body = [])
162
170
  super()
163
171
 
164
- @header = Header.new(self, header)
172
+ @headers = Headers.new
173
+
174
+ headers&.each do |key, value|
175
+ @headers[key] = value
176
+ end
165
177
 
166
178
  self.body, self.status = body, status
167
179
 
@@ -175,10 +187,10 @@ module ActionDispatch # :nodoc:
175
187
  yield self if block_given?
176
188
  end
177
189
 
178
- def has_header?(key); headers.key? key; end
179
- def get_header(key); headers[key]; end
180
- def set_header(key, v); headers[key] = v; end
181
- def delete_header(key); headers.delete key; end
190
+ def has_header?(key); @headers.key? key; end
191
+ def get_header(key); @headers[key]; end
192
+ def set_header(key, v); @headers[key] = v; end
193
+ def delete_header(key); @headers.delete key; end
182
194
 
183
195
  def await_commit
184
196
  synchronize do
@@ -281,7 +293,7 @@ module ActionDispatch # :nodoc:
281
293
  @status
282
294
  end
283
295
 
284
- # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
296
+ # Returns a string to ensure compatibility with +Net::HTTPResponse+.
285
297
  def code
286
298
  @status.to_s
287
299
  end
@@ -384,7 +396,7 @@ module ActionDispatch # :nodoc:
384
396
  # status, headers, body = *response
385
397
  def to_a
386
398
  commit!
387
- rack_response @status, @header.to_hash
399
+ rack_response @status, @headers.to_hash
388
400
  end
389
401
  alias prepare! to_a
390
402
 
@@ -451,8 +463,7 @@ module ActionDispatch # :nodoc:
451
463
  # our last chance.
452
464
  commit! unless committed?
453
465
 
454
- headers.freeze
455
- request.commit_cookie_jar! unless committed?
466
+ @request.commit_cookie_jar! unless committed?
456
467
  end
457
468
 
458
469
  def build_buffer(response, body)
@@ -476,10 +487,6 @@ module ActionDispatch # :nodoc:
476
487
  @response = response
477
488
  end
478
489
 
479
- def each(*args, &block)
480
- @response.each(*args, &block)
481
- end
482
-
483
490
  def close
484
491
  # Rack "close" maps to Response#abort, and *not* Response#close
485
492
  # (which is used when the controller's finished writing)
@@ -490,35 +497,45 @@ module ActionDispatch # :nodoc:
490
497
  @response.body
491
498
  end
492
499
 
500
+ BODY_METHODS = { to_ary: true, each: true, call: true, to_path: true }
501
+
493
502
  def respond_to?(method, include_private = false)
494
- if method.to_sym == :to_path
503
+ if BODY_METHODS.key?(method)
495
504
  @response.stream.respond_to?(method)
496
505
  else
497
506
  super
498
507
  end
499
508
  end
500
509
 
501
- def to_path
502
- @response.stream.to_path
510
+ def to_ary
511
+ @response.stream.to_ary
503
512
  end
504
513
 
505
- def to_ary
506
- nil
514
+ def each(*args, &block)
515
+ @response.each(*args, &block)
516
+ end
517
+
518
+ def call(*arguments, &block)
519
+ @response.stream.call(*arguments, &block)
520
+ end
521
+
522
+ def to_path
523
+ @response.stream.to_path
507
524
  end
508
525
  end
509
526
 
510
527
  def handle_no_content!
511
528
  if NO_CONTENT_CODES.include?(@status)
512
- @header.delete CONTENT_TYPE
513
- @header.delete "Content-Length"
529
+ @headers.delete CONTENT_TYPE
530
+ @headers.delete "Content-Length"
514
531
  end
515
532
  end
516
533
 
517
- def rack_response(status, header)
534
+ def rack_response(status, headers)
518
535
  if NO_CONTENT_CODES.include?(status)
519
- [status, header, []]
536
+ [status, headers, []]
520
537
  else
521
- [status, header, RackBody.new(self)]
538
+ [status, headers, RackBody.new(self)]
522
539
  end
523
540
  end
524
541
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActionDispatch
4
4
  module Http
5
+ # = Action Dispatch HTTP \UploadedFile
6
+ #
5
7
  # Models uploaded files.
6
8
  #
7
9
  # The actual file is accessible via the +tempfile+ accessor, though some
@@ -28,6 +30,8 @@ module ActionDispatch
28
30
  @tempfile = hash[:tempfile]
29
31
  raise(ArgumentError, ":tempfile is required") unless @tempfile
30
32
 
33
+ @content_type = hash[:type]
34
+
31
35
  if hash[:filename]
32
36
  @original_filename = hash[:filename].dup
33
37
 
@@ -40,8 +44,17 @@ module ActionDispatch
40
44
  @original_filename = nil
41
45
  end
42
46
 
43
- @content_type = hash[:type]
44
- @headers = hash[:head]
47
+ if hash[:head]
48
+ @headers = hash[:head].dup
49
+
50
+ begin
51
+ @headers.encode!(Encoding::UTF_8)
52
+ rescue EncodingError
53
+ @headers.force_encoding(Encoding::UTF_8)
54
+ end
55
+ else
56
+ @headers = nil
57
+ end
45
58
  end
46
59
 
47
60
  # Shortcut for +tempfile.read+.