actionpack 7.0.8.4 → 7.1.3.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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +358 -362
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  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 +61 -18
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +3 -3
  12. data/lib/abstract_controller/translation.rb +1 -20
  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 +5 -3
  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/content_security_policy.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +2 -0
  23. data/lib/action_controller/metal/default_headers.rb +2 -0
  24. data/lib/action_controller/metal/etag_with_flash.rb +2 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  26. data/lib/action_controller/metal/exceptions.rb +8 -0
  27. data/lib/action_controller/metal/head.rb +8 -6
  28. data/lib/action_controller/metal/helpers.rb +3 -14
  29. data/lib/action_controller/metal/http_authentication.rb +17 -8
  30. data/lib/action_controller/metal/implicit_render.rb +5 -3
  31. data/lib/action_controller/metal/instrumentation.rb +8 -1
  32. data/lib/action_controller/metal/live.rb +24 -0
  33. data/lib/action_controller/metal/mime_responds.rb +2 -2
  34. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  35. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  36. data/lib/action_controller/metal/redirecting.rb +7 -7
  37. data/lib/action_controller/metal/renderers.rb +2 -2
  38. data/lib/action_controller/metal/rendering.rb +0 -7
  39. data/lib/action_controller/metal/request_forgery_protection.rb +139 -50
  40. data/lib/action_controller/metal/rescue.rb +2 -0
  41. data/lib/action_controller/metal/streaming.rb +70 -30
  42. data/lib/action_controller/metal/strong_parameters.rb +132 -52
  43. data/lib/action_controller/metal/url_for.rb +7 -0
  44. data/lib/action_controller/metal.rb +79 -21
  45. data/lib/action_controller/railtie.rb +22 -9
  46. data/lib/action_controller/renderer.rb +98 -65
  47. data/lib/action_controller/test_case.rb +15 -5
  48. data/lib/action_controller.rb +8 -1
  49. data/lib/action_dispatch/constants.rb +32 -0
  50. data/lib/action_dispatch/deprecator.rb +7 -0
  51. data/lib/action_dispatch/http/cache.rb +1 -3
  52. data/lib/action_dispatch/http/content_security_policy.rb +9 -8
  53. data/lib/action_dispatch/http/filter_parameters.rb +11 -5
  54. data/lib/action_dispatch/http/headers.rb +2 -0
  55. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  56. data/lib/action_dispatch/http/mime_type.rb +35 -12
  57. data/lib/action_dispatch/http/mime_types.rb +3 -1
  58. data/lib/action_dispatch/http/parameters.rb +1 -1
  59. data/lib/action_dispatch/http/permissions_policy.rb +38 -16
  60. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  61. data/lib/action_dispatch/http/request.rb +48 -14
  62. data/lib/action_dispatch/http/response.rb +80 -59
  63. data/lib/action_dispatch/http/upload.rb +2 -0
  64. data/lib/action_dispatch/journey/formatter.rb +8 -2
  65. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  66. data/lib/action_dispatch/journey/route.rb +3 -2
  67. data/lib/action_dispatch/journey/router.rb +9 -8
  68. data/lib/action_dispatch/journey/routes.rb +2 -2
  69. data/lib/action_dispatch/log_subscriber.rb +23 -0
  70. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  71. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  72. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  73. data/lib/action_dispatch/middleware/cookies.rb +81 -98
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  75. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  76. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  77. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  78. data/lib/action_dispatch/middleware/executor.rb +1 -1
  79. data/lib/action_dispatch/middleware/flash.rb +7 -0
  80. data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
  81. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  82. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  83. data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
  84. data/lib/action_dispatch/middleware/request_id.rb +2 -0
  85. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +19 -15
  91. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  92. data/lib/action_dispatch/middleware/stack.rb +7 -2
  93. data/lib/action_dispatch/middleware/static.rb +12 -8
  94. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  95. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  96. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  104. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  107. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
  108. data/lib/action_dispatch/railtie.rb +14 -4
  109. data/lib/action_dispatch/request/session.rb +16 -6
  110. data/lib/action_dispatch/request/utils.rb +8 -3
  111. data/lib/action_dispatch/routing/inspector.rb +54 -6
  112. data/lib/action_dispatch/routing/mapper.rb +35 -24
  113. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  114. data/lib/action_dispatch/routing/redirection.rb +15 -6
  115. data/lib/action_dispatch/routing/route_set.rb +52 -22
  116. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  117. data/lib/action_dispatch/routing/url_for.rb +5 -1
  118. data/lib/action_dispatch/routing.rb +7 -7
  119. data/lib/action_dispatch/system_test_case.rb +3 -3
  120. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  121. data/lib/action_dispatch/system_testing/driver.rb +13 -21
  122. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  123. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  124. data/lib/action_dispatch/testing/assertions/response.rb +13 -6
  125. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  126. data/lib/action_dispatch/testing/assertions.rb +3 -1
  127. data/lib/action_dispatch/testing/integration.rb +27 -17
  128. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  129. data/lib/action_dispatch/testing/test_process.rb +4 -3
  130. data/lib/action_dispatch/testing/test_request.rb +1 -1
  131. data/lib/action_dispatch/testing/test_response.rb +23 -9
  132. data/lib/action_dispatch.rb +37 -4
  133. data/lib/action_pack/gem_version.rb +3 -3
  134. data/lib/action_pack/version.rb +1 -1
  135. data/lib/action_pack.rb +1 -1
  136. metadata +62 -26
@@ -11,6 +11,7 @@ module Mime
11
11
  def initialize
12
12
  @mimes = []
13
13
  @symbols = []
14
+ @symbols_set = Set.new
14
15
  end
15
16
 
16
17
  def each(&block)
@@ -19,17 +20,25 @@ module Mime
19
20
 
20
21
  def <<(type)
21
22
  @mimes << type
22
- @symbols << type.to_sym
23
+ sym_type = type.to_sym
24
+ @symbols << sym_type
25
+ @symbols_set << sym_type
23
26
  end
24
27
 
25
28
  def delete_if
26
29
  @mimes.delete_if do |x|
27
30
  if yield x
28
- @symbols.delete(x.to_sym)
31
+ sym_type = x.to_sym
32
+ @symbols.delete(sym_type)
33
+ @symbols_set.delete(sym_type)
29
34
  true
30
35
  end
31
36
  end
32
37
  end
38
+
39
+ def valid_symbols?(symbols) # :nodoc
40
+ symbols.all? { |s| @symbols_set.include?(s) }
41
+ end
33
42
  end
34
43
 
35
44
  SET = Mimes.new
@@ -42,6 +51,14 @@ module Mime
42
51
  Type.lookup_by_extension(type)
43
52
  end
44
53
 
54
+ def symbols
55
+ SET.symbols
56
+ end
57
+
58
+ def valid_symbols?(symbols) # :nodoc:
59
+ SET.valid_symbols?(symbols)
60
+ end
61
+
45
62
  def fetch(type, &block)
46
63
  return type if type.is_a?(Type)
47
64
  EXTENSION_LOOKUP.fetch(type.to_s, &block)
@@ -135,14 +152,18 @@ module Mime
135
152
 
136
153
  class << self
137
154
  TRAILING_STAR_REGEXP = /^(text|application)\/\*/
138
- PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
155
+ # all media-type parameters need to be before the q-parameter
156
+ # https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2
157
+ PARAMETER_SEPARATOR_REGEXP = /;\s*q="?/
158
+ ACCEPT_HEADER_REGEXP = /[^,\s"](?:[^,"]|"[^"]*")*/
139
159
 
140
160
  def register_callback(&block)
141
161
  @register_callbacks << block
142
162
  end
143
163
 
144
164
  def lookup(string)
145
- LOOKUP[string] || Type.new(string)
165
+ # fallback to the media-type without parameters if it was not found
166
+ LOOKUP[string] || LOOKUP[string.split(";", 2)[0]&.rstrip] || Type.new(string)
146
167
  end
147
168
 
148
169
  def lookup_by_extension(extension)
@@ -171,12 +192,14 @@ module Mime
171
192
 
172
193
  def parse(accept_header)
173
194
  if !accept_header.include?(",")
174
- accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
175
- return [] unless accept_header
176
- parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
195
+ if (index = accept_header.index(PARAMETER_SEPARATOR_REGEXP))
196
+ accept_header = accept_header[0, index].strip
197
+ end
198
+ return [] if accept_header.blank?
199
+ parse_trailing_star(accept_header) || Array(Mime::Type.lookup(accept_header))
177
200
  else
178
201
  list, index = [], 0
179
- accept_header.split(",").each do |header|
202
+ accept_header.scan(ACCEPT_HEADER_REGEXP).each do |header|
180
203
  params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
181
204
 
182
205
  next unless params
@@ -199,10 +222,10 @@ module Mime
199
222
  end
200
223
 
201
224
  # For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
202
- # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]</tt>.
225
+ # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
203
226
  #
204
227
  # For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
205
- # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>.
228
+ # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
206
229
  def parse_data_with_trailing_star(type)
207
230
  Mime::SET.select { |m| m.match?(type) }
208
231
  end
@@ -225,7 +248,7 @@ module Mime
225
248
  attr_reader :hash
226
249
 
227
250
  MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
228
- MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?#{MIME_NAME}#{Regexp.escape('"')}?"
251
+ MIME_PARAMETER_VALUE = "(?:#{MIME_NAME}|\"[^\"\r\\\\]*\")"
229
252
  MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?"
230
253
  MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?>#{MIME_PARAMETER})*\s*)\z/
231
254
 
@@ -291,7 +314,7 @@ module Mime
291
314
  end
292
315
 
293
316
  def html?
294
- (symbol == :html) || /html/.match?(@string)
317
+ (symbol == :html) || @string.include?("html")
295
318
  end
296
319
 
297
320
  def all?; false; end
@@ -18,6 +18,7 @@ Mime::Type.register "image/gif", :gif, [], %w(gif)
18
18
  Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
19
19
  Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
20
20
  Mime::Type.register "image/svg+xml", :svg
21
+ Mime::Type.register "image/webp", :webp, [], %w(webp)
21
22
 
22
23
  Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
23
24
 
@@ -43,7 +44,8 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
43
44
 
44
45
  # https://www.ietf.org/rfc/rfc4627.txt
45
46
  # http://www.json.org/JSONRequest.html
46
- Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
47
+ # https://www.ietf.org/rfc/rfc7807.txt
48
+ Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/problem+json )
47
49
 
48
50
  Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
49
51
  Mime::Type.register "application/zip", :zip, [], %w(zip)
@@ -76,7 +76,7 @@ module ActionDispatch
76
76
  end
77
77
 
78
78
  # Returns a hash with the \parameters used to form the \path of the request.
79
- # Returned hash keys are strings:
79
+ # Returned hash keys are symbols:
80
80
  #
81
81
  # { action: "my_action", controller: "my_controller" }
82
82
  def path_parameters
@@ -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,32 +21,30 @@ 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
38
  return response if policy_present?(headers)
41
39
 
40
+ request = ActionDispatch::Request.new(env)
41
+
42
42
  if policy = request.permissions_policy
43
- headers[POLICY] = policy.build(request.controller_instance)
43
+ headers[ActionDispatch::Constants::FEATURE_POLICY] = policy.build(request.controller_instance)
44
44
  end
45
45
 
46
46
  if policy_empty?(policy)
47
- headers.delete(POLICY)
47
+ headers.delete(ActionDispatch::Constants::FEATURE_POLICY)
48
48
  end
49
49
 
50
50
  response
@@ -52,7 +52,7 @@ module ActionDispatch # :nodoc:
52
52
 
53
53
  private
54
54
  def policy_present?(headers)
55
- headers[POLICY]
55
+ headers[ActionDispatch::Constants::FEATURE_POLICY]
56
56
  end
57
57
 
58
58
  def policy_empty?(policy)
@@ -78,7 +78,7 @@ module ActionDispatch # :nodoc:
78
78
  }.freeze
79
79
 
80
80
  # List of available permissions can be found at
81
- # 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
82
82
  DIRECTIVES = {
83
83
  accelerometer: "accelerometer",
84
84
  ambient_light_sensor: "ambient-light-sensor",
@@ -88,15 +88,18 @@ module ActionDispatch # :nodoc:
88
88
  fullscreen: "fullscreen",
89
89
  geolocation: "geolocation",
90
90
  gyroscope: "gyroscope",
91
+ hid: "hid",
92
+ idle_detection: "idle_detection",
91
93
  magnetometer: "magnetometer",
92
94
  microphone: "microphone",
93
95
  midi: "midi",
94
96
  payment: "payment",
95
97
  picture_in_picture: "picture-in-picture",
96
- speaker: "speaker",
98
+ screen_wake_lock: "screen-wake-lock",
99
+ serial: "serial",
100
+ sync_xhr: "sync-xhr",
97
101
  usb: "usb",
98
- vibrate: "vibrate",
99
- vr: "vr",
102
+ web_share: "web-share",
100
103
  }.freeze
101
104
 
102
105
  private_constant :MAPPINGS, :DIRECTIVES
@@ -122,6 +125,25 @@ module ActionDispatch # :nodoc:
122
125
  end
123
126
  end
124
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
+
125
147
  def build(context = nil)
126
148
  build_directives(context).compact.join("; ")
127
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
 
@@ -145,6 +145,18 @@ module ActionDispatch
145
145
  @request_method ||= check_method(super)
146
146
  end
147
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
+
148
160
  def routes # :nodoc:
149
161
  get_header("action_dispatch.routes")
150
162
  end
@@ -179,13 +191,6 @@ module ActionDispatch
179
191
  get_header "action_dispatch.http_auth_salt"
180
192
  end
181
193
 
182
- def show_exceptions? # :nodoc:
183
- # We're treating `nil` as "unset", and we want the default setting to be
184
- # `true`. This logic should be extracted to `env_config` and calculated
185
- # once.
186
- !(get_header("action_dispatch.show_exceptions") == false)
187
- end
188
-
189
194
  # Returns a symbol form of the #request_method.
190
195
  def request_method_symbol
191
196
  HTTP_METHOD_LOOKUP[request_method]
@@ -194,9 +199,20 @@ module ActionDispatch
194
199
  # Returns the original value of the environment's REQUEST_METHOD,
195
200
  # even if it was overridden by middleware. See #request_method for
196
201
  # more information.
197
- def method
198
- @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
199
214
  end
215
+ ruby2_keywords(:method)
200
216
 
201
217
  # Returns a symbol form of the #method.
202
218
  def method_symbol
@@ -267,6 +283,7 @@ module ActionDispatch
267
283
 
268
284
  # Returns the content length of the request as an integer.
269
285
  def content_length
286
+ return raw_post.bytesize if headers.key?("Transfer-Encoding")
270
287
  super.to_i
271
288
  end
272
289
 
@@ -321,9 +338,8 @@ module ActionDispatch
321
338
  # work with raw requests directly.
322
339
  def raw_post
323
340
  unless has_header? "RAW_POST_DATA"
324
- raw_post_body = body
325
- set_header("RAW_POST_DATA", raw_post_body.read(content_length))
326
- raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
341
+ set_header("RAW_POST_DATA", read_body_stream)
342
+ body_stream.rewind if body_stream.respond_to?(:rewind)
327
343
  end
328
344
  get_header "RAW_POST_DATA"
329
345
  end
@@ -357,6 +373,7 @@ module ActionDispatch
357
373
 
358
374
  def reset_session
359
375
  session.destroy
376
+ reset_csrf_token
360
377
  end
361
378
 
362
379
  def session=(session) # :nodoc:
@@ -428,15 +445,32 @@ module ActionDispatch
428
445
  "#<#{self.class.name} #{method} #{original_url.dump} for #{remote_ip}>"
429
446
  end
430
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
+
431
456
  private
432
457
  def check_method(name)
433
- 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
+
434
462
  name
435
463
  end
436
464
 
437
465
  def default_session
438
466
  Session.disabled(self)
439
467
  end
468
+
469
+ def read_body_stream
470
+ body_stream.rewind if body_stream.respond_to?(:rewind)
471
+ return body_stream.read if headers.key?("Transfer-Encoding") # Read body stream until EOF if "Transfer-Encoding" is present
472
+ body_stream.read(content_length)
473
+ end
440
474
  end
441
475
  end
442
476
 
@@ -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,14 +497,28 @@ 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
 
510
+ def to_ary
511
+ @response.stream.to_ary
512
+ end
513
+
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
+
501
522
  def to_path
502
523
  @response.stream.to_path
503
524
  end
@@ -505,16 +526,16 @@ module ActionDispatch # :nodoc:
505
526
 
506
527
  def handle_no_content!
507
528
  if NO_CONTENT_CODES.include?(@status)
508
- @header.delete CONTENT_TYPE
509
- @header.delete "Content-Length"
529
+ @headers.delete CONTENT_TYPE
530
+ @headers.delete "Content-Length"
510
531
  end
511
532
  end
512
533
 
513
- def rack_response(status, header)
534
+ def rack_response(status, headers)
514
535
  if NO_CONTENT_CODES.include?(status)
515
- [status, header, []]
536
+ [status, headers, []]
516
537
  else
517
- [status, header, RackBody.new(self)]
538
+ [status, headers, RackBody.new(self)]
518
539
  end
519
540
  end
520
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