actionpack 8.0.2.1 → 8.1.0.beta1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +251 -141
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +4 -2
  5. data/lib/abstract_controller/base.rb +11 -14
  6. data/lib/abstract_controller/caching.rb +6 -3
  7. data/lib/abstract_controller/collector.rb +1 -1
  8. data/lib/abstract_controller/logger.rb +2 -1
  9. data/lib/action_controller/base.rb +1 -1
  10. data/lib/action_controller/caching.rb +1 -2
  11. data/lib/action_controller/form_builder.rb +1 -1
  12. data/lib/action_controller/log_subscriber.rb +7 -0
  13. data/lib/action_controller/metal/allow_browser.rb +1 -1
  14. data/lib/action_controller/metal/conditional_get.rb +25 -0
  15. data/lib/action_controller/metal/data_streaming.rb +1 -3
  16. data/lib/action_controller/metal/exceptions.rb +5 -0
  17. data/lib/action_controller/metal/flash.rb +1 -4
  18. data/lib/action_controller/metal/head.rb +3 -1
  19. data/lib/action_controller/metal/live.rb +0 -6
  20. data/lib/action_controller/metal/permissions_policy.rb +9 -0
  21. data/lib/action_controller/metal/rate_limiting.rb +22 -7
  22. data/lib/action_controller/metal/redirecting.rb +63 -7
  23. data/lib/action_controller/metal/renderers.rb +27 -6
  24. data/lib/action_controller/metal/rendering.rb +8 -2
  25. data/lib/action_controller/metal/request_forgery_protection.rb +18 -10
  26. data/lib/action_controller/metal/rescue.rb +9 -0
  27. data/lib/action_controller/railtie.rb +2 -6
  28. data/lib/action_controller/renderer.rb +0 -1
  29. data/lib/action_dispatch/constants.rb +6 -0
  30. data/lib/action_dispatch/http/cache.rb +111 -1
  31. data/lib/action_dispatch/http/content_security_policy.rb +13 -1
  32. data/lib/action_dispatch/http/filter_parameters.rb +5 -3
  33. data/lib/action_dispatch/http/mime_negotiation.rb +8 -3
  34. data/lib/action_dispatch/http/mime_types.rb +1 -0
  35. data/lib/action_dispatch/http/param_builder.rb +28 -27
  36. data/lib/action_dispatch/http/parameters.rb +3 -3
  37. data/lib/action_dispatch/http/permissions_policy.rb +4 -0
  38. data/lib/action_dispatch/http/query_parser.rb +12 -10
  39. data/lib/action_dispatch/http/request.rb +10 -5
  40. data/lib/action_dispatch/http/response.rb +65 -17
  41. data/lib/action_dispatch/http/url.rb +101 -5
  42. data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
  43. data/lib/action_dispatch/journey/gtg/transition_table.rb +29 -39
  44. data/lib/action_dispatch/journey/nodes/node.rb +2 -1
  45. data/lib/action_dispatch/journey/route.rb +45 -31
  46. data/lib/action_dispatch/journey/router/utils.rb +8 -14
  47. data/lib/action_dispatch/journey/router.rb +59 -81
  48. data/lib/action_dispatch/journey/routes.rb +7 -0
  49. data/lib/action_dispatch/journey/visitors.rb +55 -23
  50. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  51. data/lib/action_dispatch/middleware/cookies.rb +4 -2
  52. data/lib/action_dispatch/middleware/debug_exceptions.rb +10 -2
  53. data/lib/action_dispatch/middleware/debug_view.rb +11 -0
  54. data/lib/action_dispatch/middleware/exception_wrapper.rb +14 -8
  55. data/lib/action_dispatch/middleware/executor.rb +12 -2
  56. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -6
  57. data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
  58. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  59. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -2
  60. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  61. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  62. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  63. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +1 -0
  64. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  65. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  66. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  67. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  68. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  69. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  70. data/lib/action_dispatch/railtie.rb +10 -2
  71. data/lib/action_dispatch/routing/inspector.rb +4 -1
  72. data/lib/action_dispatch/routing/mapper.rb +323 -173
  73. data/lib/action_dispatch/routing/route_set.rb +3 -6
  74. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
  75. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  76. data/lib/action_dispatch/testing/assertions/response.rb +14 -0
  77. data/lib/action_dispatch/testing/assertions/routing.rb +11 -3
  78. data/lib/action_pack/gem_version.rb +3 -3
  79. metadata +11 -10
@@ -63,6 +63,114 @@ module ActionDispatch
63
63
  success
64
64
  end
65
65
  end
66
+
67
+ def cache_control_directives
68
+ @cache_control_directives ||= CacheControlDirectives.new(get_header("HTTP_CACHE_CONTROL"))
69
+ end
70
+
71
+ # Represents the HTTP Cache-Control header for requests,
72
+ # providing methods to access various cache control directives
73
+ # Reference: https://www.rfc-editor.org/rfc/rfc9111.html#name-request-directives
74
+ class CacheControlDirectives
75
+ def initialize(cache_control_header)
76
+ @only_if_cached = false
77
+ @no_cache = false
78
+ @no_store = false
79
+ @no_transform = false
80
+ @max_age = nil
81
+ @max_stale = nil
82
+ @min_fresh = nil
83
+ @stale_if_error = false
84
+ parse_directives(cache_control_header)
85
+ end
86
+
87
+ # Returns true if the only-if-cached directive is present.
88
+ # This directive indicates that the client only wishes to obtain a
89
+ # stored response. If a valid stored response is not available,
90
+ # the server should respond with a 504 (Gateway Timeout) status.
91
+ def only_if_cached?
92
+ @only_if_cached
93
+ end
94
+
95
+ # Returns true if the no-cache directive is present.
96
+ # This directive indicates that a cache must not use the response
97
+ # to satisfy subsequent requests without successful validation on the origin server.
98
+ def no_cache?
99
+ @no_cache
100
+ end
101
+
102
+ # Returns true if the no-store directive is present.
103
+ # This directive indicates that a cache must not store any part of the
104
+ # request or response.
105
+ def no_store?
106
+ @no_store
107
+ end
108
+
109
+ # Returns true if the no-transform directive is present.
110
+ # This directive indicates that a cache or proxy must not transform the payload.
111
+ def no_transform?
112
+ @no_transform
113
+ end
114
+
115
+ # Returns the value of the max-age directive.
116
+ # This directive indicates that the client is willing to accept a response
117
+ # whose age is no greater than the specified number of seconds.
118
+ attr_reader :max_age
119
+
120
+ # Returns the value of the max-stale directive.
121
+ # When max-stale is present with a value, returns that integer value.
122
+ # When max-stale is present without a value, returns true (unlimited staleness).
123
+ # When max-stale is not present, returns nil.
124
+ attr_reader :max_stale
125
+
126
+ # Returns true if max-stale directive is present (with or without a value)
127
+ def max_stale?
128
+ !@max_stale.nil?
129
+ end
130
+
131
+ # Returns true if max-stale directive is present without a value (unlimited staleness)
132
+ def max_stale_unlimited?
133
+ @max_stale == true
134
+ end
135
+
136
+ # Returns the value of the min-fresh directive.
137
+ # This directive indicates that the client is willing to accept a response
138
+ # whose freshness lifetime is no less than its current age plus the specified time in seconds.
139
+ attr_reader :min_fresh
140
+
141
+ # Returns the value of the stale-if-error directive.
142
+ # This directive indicates that the client is willing to accept a stale response
143
+ # if the check for a fresh one fails with an error for the specified number of seconds.
144
+ attr_reader :stale_if_error
145
+
146
+ private
147
+ def parse_directives(header_value)
148
+ return unless header_value
149
+
150
+ header_value.delete(" ").downcase.split(",").each do |directive|
151
+ name, value = directive.split("=", 2)
152
+
153
+ case name
154
+ when "max-age"
155
+ @max_age = value.to_i
156
+ when "min-fresh"
157
+ @min_fresh = value.to_i
158
+ when "stale-if-error"
159
+ @stale_if_error = value.to_i
160
+ when "no-cache"
161
+ @no_cache = true
162
+ when "no-store"
163
+ @no_store = true
164
+ when "no-transform"
165
+ @no_transform = true
166
+ when "only-if-cached"
167
+ @only_if_cached = true
168
+ when "max-stale"
169
+ @max_stale = value ? value.to_i : true
170
+ end
171
+ end
172
+ end
173
+ end
66
174
  end
67
175
 
68
176
  module Response
@@ -142,7 +250,7 @@ module ActionDispatch
142
250
  private
143
251
  DATE = "Date"
144
252
  LAST_MODIFIED = "Last-Modified"
145
- SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate])
253
+ SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate must-understand])
146
254
 
147
255
  def generate_weak_etag(validators)
148
256
  "W/#{generate_strong_etag(validators)}"
@@ -187,6 +295,7 @@ module ActionDispatch
187
295
  PRIVATE = "private"
188
296
  MUST_REVALIDATE = "must-revalidate"
189
297
  IMMUTABLE = "immutable"
298
+ MUST_UNDERSTAND = "must-understand"
190
299
 
191
300
  def handle_conditional_get!
192
301
  # Normally default cache control setting is handled by ETag middleware. But, if
@@ -221,6 +330,7 @@ module ActionDispatch
221
330
 
222
331
  if control[:no_store]
223
332
  options << PRIVATE if control[:private]
333
+ options << MUST_UNDERSTAND if control[:must_understand]
224
334
  options << NO_STORE
225
335
  elsif control[:no_cache]
226
336
  options << PUBLIC if control[:public]
@@ -171,6 +171,8 @@ module ActionDispatch # :nodoc:
171
171
  worker_src: "worker-src"
172
172
  }.freeze
173
173
 
174
+ HASH_SOURCE_ALGORITHM_PREFIXES = ["sha256-", "sha384-", "sha512-"].freeze
175
+
174
176
  DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
175
177
 
176
178
  private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
@@ -305,7 +307,13 @@ module ActionDispatch # :nodoc:
305
307
  case source
306
308
  when Symbol
307
309
  apply_mapping(source)
308
- when String, Proc
310
+ when String
311
+ if hash_source?(source)
312
+ "'#{source}'"
313
+ else
314
+ source
315
+ end
316
+ when Proc
309
317
  source
310
318
  else
311
319
  raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
@@ -374,5 +382,9 @@ module ActionDispatch # :nodoc:
374
382
  def nonce_directive?(directive, nonce_directives)
375
383
  nonce_directives.include?(directive)
376
384
  end
385
+
386
+ def hash_source?(source)
387
+ source.start_with?(*HASH_SOURCE_ALGORITHM_PREFIXES)
388
+ end
377
389
  end
378
390
  end
@@ -17,9 +17,11 @@ module ActionDispatch
17
17
  # For more information about filter behavior, see
18
18
  # ActiveSupport::ParameterFilter.
19
19
  module FilterParameters
20
- ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
21
- NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
22
- NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
20
+ # :stopdoc:
21
+ ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"]
22
+ NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new
23
+ NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH
24
+ # :startdoc:
23
25
 
24
26
  def initialize
25
27
  super
@@ -56,9 +56,14 @@ module ActionDispatch
56
56
 
57
57
  # Returns the MIME type for the format used in the request.
58
58
  #
59
- # GET /posts/5.xml | request.format => Mime[:xml]
60
- # GET /posts/5.xhtml | request.format => Mime[:html]
61
- # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
59
+ # # GET /posts/5.xml
60
+ # request.format # => Mime[:xml]
61
+ #
62
+ # # GET /posts/5.xhtml
63
+ # request.format # => Mime[:html]
64
+ #
65
+ # # GET /posts/5
66
+ # request.format # => Mime[:html] or Mime[:js], or request.accepts.first
62
67
  #
63
68
  def format(_view_path = nil)
64
69
  formats.first || Mime::NullType.instance
@@ -13,6 +13,7 @@ Mime::Type.register "text/calendar", :ics
13
13
  Mime::Type.register "text/csv", :csv
14
14
  Mime::Type.register "text/vcard", :vcf
15
15
  Mime::Type.register "text/vtt", :vtt, %w(vtt)
16
+ Mime::Type.register "text/markdown", :md, [], %w(md markdown)
16
17
 
17
18
  Mime::Type.register "image/png", :png, [], %w(png)
18
19
  Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
@@ -16,15 +16,27 @@ module ActionDispatch
16
16
  @param_depth_limit = param_depth_limit
17
17
  end
18
18
 
19
- cattr_accessor :ignore_leading_brackets
20
-
21
- LEADING_BRACKETS_COMPAT = defined?(::Rack::RELEASE) && ::Rack::RELEASE.to_s.start_with?("2.")
22
-
23
19
  cattr_accessor :default
24
20
  self.default = make_default(100)
25
21
 
26
22
  class << self
27
23
  delegate :from_query_string, :from_pairs, :from_hash, to: :default
24
+
25
+ def ignore_leading_brackets
26
+ ActionDispatch.deprecator.warn <<~MSG
27
+ ActionDispatch::ParamBuilder.ignore_leading_brackets is deprecated and have no effect and will be removed in Rails 8.2.
28
+ MSG
29
+
30
+ @ignore_leading_brackets
31
+ end
32
+
33
+ def ignore_leading_brackets=(value)
34
+ ActionDispatch.deprecator.warn <<~MSG
35
+ ActionDispatch::ParamBuilder.ignore_leading_brackets is deprecated and have no effect and will be removed in Rails 8.2.
36
+ MSG
37
+
38
+ @ignore_leading_brackets = value
39
+ end
28
40
  end
29
41
 
30
42
  def from_query_string(qs, separator: nil, encoding_template: nil)
@@ -69,30 +81,15 @@ module ActionDispatch
69
81
  # nil name, treat same as empty string (required by tests)
70
82
  k = after = ""
71
83
  elsif depth == 0
72
- if ignore_leading_brackets || (ignore_leading_brackets.nil? && LEADING_BRACKETS_COMPAT)
73
- # Rack 2 compatible behavior, ignore leading brackets
74
- if name =~ /\A[\[\]]*([^\[\]]+)\]*/
75
- k = $1
76
- after = $' || ""
77
-
78
- if !ignore_leading_brackets && (k != $& || !after.empty? && !after.start_with?("["))
79
- ActionDispatch.deprecator.warn("Skipping over leading brackets in parameter name #{name.inspect} is deprecated and will parse differently in Rails 8.1 or Rack 3.0.")
80
- end
81
- else
82
- k = name
83
- after = ""
84
- end
84
+ # Start of parsing, don't treat [] or [ at start of string specially
85
+ if start = name.index("[", 1)
86
+ # Start of parameter nesting, use part before brackets as key
87
+ k = name[0, start]
88
+ after = name[start, name.length]
85
89
  else
86
- # Start of parsing, don't treat [] or [ at start of string specially
87
- if start = name.index("[", 1)
88
- # Start of parameter nesting, use part before brackets as key
89
- k = name[0, start]
90
- after = name[start, name.length]
91
- else
92
- # Plain parameter with no nesting
93
- k = name
94
- after = ""
95
- end
90
+ # Plain parameter with no nesting
91
+ k = name
92
+ after = ""
96
93
  end
97
94
  elsif name.start_with?("[]")
98
95
  # Array nesting
@@ -111,6 +108,10 @@ module ActionDispatch
111
108
 
112
109
  return if k.empty?
113
110
 
111
+ unless k.valid_encoding?
112
+ raise InvalidParameterError, "Invalid encoding for parameter: #{k}"
113
+ end
114
+
114
115
  if depth == 0 && String === v
115
116
  # We have to wait until we've found the top part of the name,
116
117
  # because that's what the encoding template is configured with
@@ -65,14 +65,14 @@ module ActionDispatch
65
65
  alias :params :parameters
66
66
 
67
67
  def path_parameters=(parameters) # :nodoc:
68
- delete_header("action_dispatch.request.parameters")
68
+ @env.delete("action_dispatch.request.parameters")
69
69
 
70
70
  parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action])
71
71
  # If any of the path parameters has an invalid encoding then raise since it's
72
72
  # likely to trigger errors further on.
73
73
  Request::Utils.check_param_encoding(parameters)
74
74
 
75
- set_header PARAMETERS_KEY, parameters
75
+ @env[PARAMETERS_KEY] = parameters
76
76
  rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
77
77
  raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}")
78
78
  end
@@ -82,7 +82,7 @@ module ActionDispatch
82
82
  #
83
83
  # { action: "my_action", controller: "my_controller" }
84
84
  def path_parameters
85
- get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
85
+ @env[PARAMETERS_KEY] ||= {}
86
86
  end
87
87
 
88
88
  private
@@ -186,4 +186,8 @@ module ActionDispatch # :nodoc:
186
186
  end
187
187
  end
188
188
  end
189
+
190
+ ActiveSupport.on_load(:action_dispatch_request) do
191
+ include ActionDispatch::PermissionsPolicy::Request
192
+ end
189
193
  end
@@ -6,12 +6,21 @@ require "rack"
6
6
  module ActionDispatch
7
7
  class QueryParser
8
8
  DEFAULT_SEP = /& */n
9
- COMPAT_SEP = /[&;] */n
10
9
  COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n, "&;" => /[&;] */n }
11
10
 
12
- cattr_accessor :strict_query_string_separator
11
+ def self.strict_query_string_separator
12
+ ActionDispatch.deprecator.warn <<~MSG
13
+ The `strict_query_string_separator` configuration is deprecated have no effect and will be removed in Rails 8.2.
14
+ MSG
15
+ @strict_query_string_separator
16
+ end
13
17
 
14
- SEMICOLON_COMPAT = defined?(::Rack::QueryParser::DEFAULT_SEP) && ::Rack::QueryParser::DEFAULT_SEP.to_s.include?(";")
18
+ def self.strict_query_string_separator=(value)
19
+ ActionDispatch.deprecator.warn <<~MSG
20
+ The `strict_query_string_separator` configuration is deprecated have no effect and will be removed in Rails 8.2.
21
+ MSG
22
+ @strict_query_string_separator = value
23
+ end
15
24
 
16
25
  #--
17
26
  # Note this departs from WHATWG's specified parsing algorithm by
@@ -25,13 +34,6 @@ module ActionDispatch
25
34
  splitter =
26
35
  if separator
27
36
  COMMON_SEP[separator] || /[#{separator}] */n
28
- elsif strict_query_string_separator
29
- DEFAULT_SEP
30
- elsif SEMICOLON_COMPAT && s.include?(";")
31
- if strict_query_string_separator.nil?
32
- ActionDispatch.deprecator.warn("Using semicolon as a query string separator is deprecated and will not be supported in Rails 8.1 or Rack 3.0. Use `&` instead.")
33
- end
34
- COMPAT_SEP
35
37
  else
36
38
  DEFAULT_SEP
37
39
  end
@@ -25,7 +25,6 @@ module ActionDispatch
25
25
  include ActionDispatch::Http::FilterParameters
26
26
  include ActionDispatch::Http::URL
27
27
  include ActionDispatch::ContentSecurityPolicy::Request
28
- include ActionDispatch::PermissionsPolicy::Request
29
28
  include Rack::Request::Env
30
29
 
31
30
  autoload :Session, "action_dispatch/request/session"
@@ -139,7 +138,7 @@ module ActionDispatch
139
138
 
140
139
  # Populate the HTTP method lookup cache.
141
140
  HTTP_METHODS.each { |method|
142
- HTTP_METHOD_LOOKUP[method] = method.downcase.underscore.to_sym
141
+ HTTP_METHOD_LOOKUP[method] = method.downcase.tap { |m| m.tr!("-", "_") }.to_sym
143
142
  }
144
143
 
145
144
  alias raw_request_method request_method # :nodoc:
@@ -158,11 +157,17 @@ module ActionDispatch
158
157
  #
159
158
  # request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
160
159
  def route_uri_pattern
161
- get_header("action_dispatch.route_uri_pattern")
160
+ unless pattern = get_header("action_dispatch.route_uri_pattern")
161
+ route = get_header("action_dispatch.route")
162
+ return if route.nil?
163
+ pattern = route.path.spec.to_s
164
+ set_header("action_dispatch.route_uri_pattern", pattern)
165
+ end
166
+ pattern
162
167
  end
163
168
 
164
- def route_uri_pattern=(pattern) # :nodoc:
165
- set_header("action_dispatch.route_uri_pattern", pattern)
169
+ def route=(route) # :nodoc:
170
+ @env["action_dispatch.route"] = route
166
171
  end
167
172
 
168
173
  def routes # :nodoc:
@@ -46,6 +46,20 @@ module ActionDispatch # :nodoc:
46
46
  Headers = ::Rack::Utils::HeaderHash
47
47
  end
48
48
 
49
+ class << self
50
+ if ActionDispatch::Constants::UNPROCESSABLE_CONTENT == :unprocessable_content
51
+ def rack_status_code(status) # :nodoc:
52
+ status = :unprocessable_content if status == :unprocessable_entity
53
+ Rack::Utils.status_code(status)
54
+ end
55
+ else
56
+ def rack_status_code(status) # :nodoc:
57
+ status = :unprocessable_entity if status == :unprocessable_content
58
+ Rack::Utils.status_code(status)
59
+ end
60
+ end
61
+ end
62
+
49
63
  # To be deprecated:
50
64
  Header = Headers
51
65
 
@@ -105,10 +119,22 @@ module ActionDispatch # :nodoc:
105
119
  @str_body = nil
106
120
  end
107
121
 
122
+ BODY_METHODS = { to_ary: true }
123
+
124
+ def respond_to?(method, include_private = false)
125
+ if BODY_METHODS.key?(method)
126
+ @buf.respond_to?(method)
127
+ else
128
+ super
129
+ end
130
+ end
131
+
108
132
  def to_ary
109
- @buf.respond_to?(:to_ary) ?
110
- @buf.to_ary :
111
- @buf.each
133
+ if @str_body
134
+ [body]
135
+ else
136
+ @buf = @buf.to_ary
137
+ end
112
138
  end
113
139
 
114
140
  def body
@@ -245,20 +271,33 @@ module ActionDispatch # :nodoc:
245
271
 
246
272
  # Sets the HTTP status code.
247
273
  def status=(status)
248
- @status = Rack::Utils.status_code(status)
274
+ @status = Response.rack_status_code(status)
249
275
  end
250
276
 
251
277
  # Sets the HTTP response's content MIME type. For example, in the controller you
252
278
  # could write this:
253
279
  #
254
- # response.content_type = "text/plain"
280
+ # response.content_type = "text/html"
281
+ #
282
+ # This method also accepts a symbol with the extension of the MIME type:
283
+ #
284
+ # response.content_type = :html
255
285
  #
256
286
  # If a character set has been defined for this response (see #charset=) then the
257
287
  # character set information will also be included in the content type
258
288
  # information.
259
289
  def content_type=(content_type)
260
- return unless content_type
261
- new_header_info = parse_content_type(content_type.to_s)
290
+ case content_type
291
+ when NilClass
292
+ return
293
+ when Symbol
294
+ mime_type = Mime[content_type]
295
+ raise ArgumentError, "Unknown MIME type #{content_type}" unless mime_type
296
+ new_header_info = ContentTypeHeader.new(mime_type.to_s)
297
+ else
298
+ new_header_info = parse_content_type(content_type.to_s)
299
+ end
300
+
262
301
  prev_header_info = parsed_content_type_header
263
302
  charset = new_header_info.charset || prev_header_info.charset
264
303
  charset ||= self.class.default_charset unless prev_header_info.mime_type
@@ -328,7 +367,13 @@ module ActionDispatch # :nodoc:
328
367
  # Returns the content of the response as a string. This contains the contents of
329
368
  # any calls to `render`.
330
369
  def body
331
- @stream.body
370
+ if @stream.respond_to?(:to_ary)
371
+ @stream.to_ary.join
372
+ elsif @stream.respond_to?(:body)
373
+ @stream.body
374
+ else
375
+ @stream
376
+ end
332
377
  end
333
378
 
334
379
  def write(string)
@@ -337,11 +382,16 @@ module ActionDispatch # :nodoc:
337
382
 
338
383
  # Allows you to manually set or override the response body.
339
384
  def body=(body)
340
- if body.respond_to?(:to_path)
341
- @stream = body
342
- else
343
- synchronize do
344
- @stream = build_buffer self, munge_body_object(body)
385
+ # Prevent ActionController::Metal::Live::Response from committing the response prematurely.
386
+ synchronize do
387
+ if body.respond_to?(:to_str)
388
+ @stream = build_buffer(self, [body])
389
+ elsif body.respond_to?(:to_path)
390
+ @stream = body
391
+ elsif body.respond_to?(:to_ary)
392
+ @stream = build_buffer(self, body)
393
+ else
394
+ @stream = body
345
395
  end
346
396
  end
347
397
  end
@@ -482,10 +532,6 @@ module ActionDispatch # :nodoc:
482
532
  Buffer.new response, body
483
533
  end
484
534
 
485
- def munge_body_object(body)
486
- body.respond_to?(:each) ? body : [body]
487
- end
488
-
489
535
  def assign_default_content_type_and_charset!
490
536
  return if media_type
491
537
 
@@ -499,6 +545,8 @@ module ActionDispatch # :nodoc:
499
545
  @response = response
500
546
  end
501
547
 
548
+ attr :response
549
+
502
550
  def close
503
551
  # Rack "close" maps to Response#abort, and **not** Response#close (which is used
504
552
  # when the controller's finished writing)