actionpack 4.2.8 → 5.2.4.2

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 (166) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +285 -444
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller.rb +12 -5
  6. data/lib/abstract_controller/asset_paths.rb +2 -0
  7. data/lib/abstract_controller/base.rb +45 -49
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
  10. data/lib/abstract_controller/callbacks.rb +47 -31
  11. data/lib/abstract_controller/collector.rb +8 -11
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +25 -25
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
  16. data/lib/abstract_controller/rendering.rb +42 -41
  17. data/lib/abstract_controller/translation.rb +10 -7
  18. data/lib/abstract_controller/url_for.rb +2 -0
  19. data/lib/action_controller.rb +29 -21
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +27 -19
  23. data/lib/action_controller/caching.rb +14 -57
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +10 -15
  26. data/lib/action_controller/metal.rb +98 -83
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +118 -44
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +3 -3
  31. data/lib/action_controller/metal/data_streaming.rb +27 -46
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  34. data/lib/action_controller/metal/exceptions.rb +8 -14
  35. data/lib/action_controller/metal/flash.rb +4 -3
  36. data/lib/action_controller/metal/force_ssl.rb +23 -21
  37. data/lib/action_controller/metal/head.rb +21 -19
  38. data/lib/action_controller/metal/helpers.rb +24 -14
  39. data/lib/action_controller/metal/http_authentication.rb +64 -57
  40. data/lib/action_controller/metal/implicit_render.rb +62 -8
  41. data/lib/action_controller/metal/instrumentation.rb +19 -21
  42. data/lib/action_controller/metal/live.rb +90 -106
  43. data/lib/action_controller/metal/mime_responds.rb +33 -46
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +61 -53
  46. data/lib/action_controller/metal/redirecting.rb +49 -28
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +72 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +203 -92
  50. data/lib/action_controller/metal/rescue.rb +9 -16
  51. data/lib/action_controller/metal/streaming.rb +12 -10
  52. data/lib/action_controller/metal/strong_parameters.rb +582 -165
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/railtie.rb +28 -10
  56. data/lib/action_controller/railties/helpers.rb +2 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +280 -411
  60. data/lib/action_dispatch.rb +27 -19
  61. data/lib/action_dispatch/http/cache.rb +93 -47
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +26 -20
  64. data/lib/action_dispatch/http/filter_redirect.rb +10 -11
  65. data/lib/action_dispatch/http/headers.rb +55 -22
  66. data/lib/action_dispatch/http/mime_negotiation.rb +60 -41
  67. data/lib/action_dispatch/http/mime_type.rb +134 -121
  68. data/lib/action_dispatch/http/mime_types.rb +20 -6
  69. data/lib/action_dispatch/http/parameter_filter.rb +25 -11
  70. data/lib/action_dispatch/http/parameters.rb +98 -39
  71. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  72. data/lib/action_dispatch/http/request.rb +200 -118
  73. data/lib/action_dispatch/http/response.rb +225 -110
  74. data/lib/action_dispatch/http/upload.rb +12 -6
  75. data/lib/action_dispatch/http/url.rb +110 -28
  76. data/lib/action_dispatch/journey.rb +7 -5
  77. data/lib/action_dispatch/journey/formatter.rb +55 -32
  78. data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
  81. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  82. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
  85. data/lib/action_dispatch/journey/nodes/node.rb +18 -6
  86. data/lib/action_dispatch/journey/parser.rb +23 -22
  87. data/lib/action_dispatch/journey/parser.y +3 -2
  88. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  89. data/lib/action_dispatch/journey/path/pattern.rb +50 -44
  90. data/lib/action_dispatch/journey/route.rb +106 -28
  91. data/lib/action_dispatch/journey/router.rb +35 -23
  92. data/lib/action_dispatch/journey/router/utils.rb +20 -11
  93. data/lib/action_dispatch/journey/routes.rb +18 -16
  94. data/lib/action_dispatch/journey/scanner.rb +18 -15
  95. data/lib/action_dispatch/journey/visitors.rb +99 -52
  96. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  97. data/lib/action_dispatch/middleware/cookies.rb +304 -193
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
  101. data/lib/action_dispatch/middleware/executor.rb +21 -0
  102. data/lib/action_dispatch/middleware/flash.rb +78 -54
  103. data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
  104. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  105. data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
  106. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  107. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
  108. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  109. data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
  110. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  111. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
  112. data/lib/action_dispatch/middleware/ssl.rb +114 -36
  113. data/lib/action_dispatch/middleware/stack.rb +31 -44
  114. data/lib/action_dispatch/middleware/static.rb +57 -50
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  116. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  124. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
  125. data/lib/action_dispatch/railtie.rb +19 -11
  126. data/lib/action_dispatch/request/session.rb +106 -59
  127. data/lib/action_dispatch/request/utils.rb +67 -24
  128. data/lib/action_dispatch/routing.rb +17 -18
  129. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  130. data/lib/action_dispatch/routing/inspector.rb +58 -67
  131. data/lib/action_dispatch/routing/mapper.rb +734 -447
  132. data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -139
  133. data/lib/action_dispatch/routing/redirection.rb +36 -26
  134. data/lib/action_dispatch/routing/route_set.rb +321 -291
  135. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  136. data/lib/action_dispatch/routing/url_for.rb +65 -25
  137. data/lib/action_dispatch/system_test_case.rb +147 -0
  138. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  139. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  140. data/lib/action_dispatch/system_testing/server.rb +31 -0
  141. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  143. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  144. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  145. data/lib/action_dispatch/testing/assertions.rb +6 -4
  146. data/lib/action_dispatch/testing/assertions/response.rb +45 -20
  147. data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
  148. data/lib/action_dispatch/testing/integration.rb +347 -209
  149. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  150. data/lib/action_dispatch/testing/test_process.rb +28 -22
  151. data/lib/action_dispatch/testing/test_request.rb +27 -34
  152. data/lib/action_dispatch/testing/test_response.rb +35 -7
  153. data/lib/action_pack.rb +4 -2
  154. data/lib/action_pack/gem_version.rb +5 -3
  155. data/lib/action_pack/version.rb +3 -1
  156. metadata +56 -39
  157. data/lib/action_controller/metal/hide_actions.rb +0 -40
  158. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  159. data/lib/action_controller/middleware.rb +0 -39
  160. data/lib/action_controller/model_naming.rb +0 -12
  161. data/lib/action_dispatch/journey/backwards.rb +0 -5
  162. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  163. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  164. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  165. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  166. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,8 +1,9 @@
1
- require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_support/core_ext/string/filters'
3
- require 'active_support/deprecation'
4
- require 'action_dispatch/http/filter_redirect'
5
- require 'monitor'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attribute_accessors"
4
+ require "action_dispatch/http/filter_redirect"
5
+ require "action_dispatch/http/cache"
6
+ require "monitor"
6
7
 
7
8
  module ActionDispatch # :nodoc:
8
9
  # Represents an HTTP response generated by a controller action. Use it to
@@ -34,46 +35,62 @@ module ActionDispatch # :nodoc:
34
35
  # end
35
36
  # end
36
37
  class Response
38
+ class Header < DelegateClass(Hash) # :nodoc:
39
+ def initialize(response, header)
40
+ @response = response
41
+ super(header)
42
+ end
43
+
44
+ def []=(k, v)
45
+ if @response.sending? || @response.sent?
46
+ raise ActionDispatch::IllegalStateError, "header already sent"
47
+ end
48
+
49
+ super
50
+ end
51
+
52
+ def merge(other)
53
+ self.class.new @response, __getobj__.merge(other)
54
+ end
55
+
56
+ def to_hash
57
+ __getobj__.dup
58
+ end
59
+ end
60
+
37
61
  # The request that the response is responding to.
38
62
  attr_accessor :request
39
63
 
40
64
  # The HTTP status code.
41
65
  attr_reader :status
42
66
 
43
- attr_writer :sending_file
44
-
45
- # Get and set headers for this response.
46
- attr_accessor :header
67
+ # Get headers for this response.
68
+ attr_reader :header
47
69
 
48
- alias_method :headers=, :header=
49
70
  alias_method :headers, :header
50
71
 
51
- delegate :[], :[]=, :to => :@header
52
- delegate :each, :to => :@stream
72
+ delegate :[], :[]=, to: :@header
53
73
 
54
- # Sets the HTTP response's content MIME type. For example, in the controller
55
- # you could write this:
56
- #
57
- # response.content_type = "text/plain"
58
- #
59
- # If a character set has been defined for this response (see charset=) then
60
- # the character set information will also be included in the content type
61
- # information.
62
- attr_reader :content_type
63
-
64
- # The charset of the response. HTML wants to know the encoding of the
65
- # content you're giving them, so we need to send that along.
66
- attr_accessor :charset
74
+ def each(&block)
75
+ sending!
76
+ x = @stream.each(&block)
77
+ sent!
78
+ x
79
+ end
67
80
 
68
81
  CONTENT_TYPE = "Content-Type".freeze
69
82
  SET_COOKIE = "Set-Cookie".freeze
70
83
  LOCATION = "Location".freeze
71
- NO_CONTENT_CODES = [204, 304]
84
+ NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
72
85
 
73
- cattr_accessor(:default_charset) { "utf-8" }
74
- cattr_accessor(:default_headers)
86
+ cattr_accessor :default_charset, default: "utf-8"
87
+ cattr_accessor :default_headers
75
88
 
76
89
  include Rack::Response::Helpers
90
+ # Aliasing these off because AD::Http::Cache::Response defines them.
91
+ alias :_cache_control :cache_control
92
+ alias :_cache_control= :cache_control=
93
+
77
94
  include ActionDispatch::Http::FilterRedirect
78
95
  include ActionDispatch::Http::Cache::Response
79
96
  include MonitorMixin
@@ -83,20 +100,33 @@ module ActionDispatch # :nodoc:
83
100
  @response = response
84
101
  @buf = buf
85
102
  @closed = false
103
+ @str_body = nil
104
+ end
105
+
106
+ def body
107
+ @str_body ||= begin
108
+ buf = "".dup
109
+ each { |chunk| buf << chunk }
110
+ buf
111
+ end
86
112
  end
87
113
 
88
114
  def write(string)
89
115
  raise IOError, "closed stream" if closed?
90
116
 
117
+ @str_body = nil
91
118
  @response.commit!
92
119
  @buf.push string
93
120
  end
94
121
 
95
122
  def each(&block)
96
- @response.sending!
97
- x = @buf.each(&block)
98
- @response.sent!
99
- x
123
+ if @str_body
124
+ return enum_for(:each) unless block_given?
125
+
126
+ yield @str_body
127
+ else
128
+ each_chunk(&block)
129
+ end
100
130
  end
101
131
 
102
132
  def abort
@@ -110,39 +140,48 @@ module ActionDispatch # :nodoc:
110
140
  def closed?
111
141
  @closed
112
142
  end
143
+
144
+ private
145
+
146
+ def each_chunk(&block)
147
+ @buf.each(&block)
148
+ end
149
+ end
150
+
151
+ def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
152
+ header = merge_default_headers(header, default_headers)
153
+ new status, header, body
154
+ end
155
+
156
+ def self.merge_default_headers(original, default)
157
+ default.respond_to?(:merge) ? default.merge(original) : original
113
158
  end
114
159
 
115
160
  # The underlying body, as a streamable object.
116
161
  attr_reader :stream
117
162
 
118
- def initialize(status = 200, header = {}, body = [], options = {})
163
+ def initialize(status = 200, header = {}, body = [])
119
164
  super()
120
165
 
121
- default_headers = options.fetch(:default_headers, self.class.default_headers)
122
- header = merge_default_headers(header, default_headers)
166
+ @header = Header.new(self, header)
123
167
 
124
- self.body, self.header, self.status = body, header, status
168
+ self.body, self.status = body, status
125
169
 
126
- @sending_file = false
127
- @blank = false
128
170
  @cv = new_cond
129
171
  @committed = false
130
172
  @sending = false
131
173
  @sent = false
132
- @content_type = nil
133
- @charset = nil
134
-
135
- if content_type = self[CONTENT_TYPE]
136
- type, charset = content_type.split(/;\s*charset=/)
137
- @content_type = Mime::Type.lookup(type)
138
- @charset = charset || self.class.default_charset
139
- end
140
174
 
141
175
  prepare_cache_control!
142
176
 
143
177
  yield self if block_given?
144
178
  end
145
179
 
180
+ def has_header?(key); headers.key? key; end
181
+ def get_header(key); headers[key]; end
182
+ def set_header(key, v); headers[key] = v; end
183
+ def delete_header(key); headers.delete key; end
184
+
146
185
  def await_commit
147
186
  synchronize do
148
187
  @cv.wait_until { @committed }
@@ -187,7 +226,52 @@ module ActionDispatch # :nodoc:
187
226
 
188
227
  # Sets the HTTP content type.
189
228
  def content_type=(content_type)
190
- @content_type = content_type.to_s
229
+ return unless content_type
230
+ new_header_info = parse_content_type(content_type.to_s)
231
+ prev_header_info = parsed_content_type_header
232
+ charset = new_header_info.charset || prev_header_info.charset
233
+ charset ||= self.class.default_charset unless prev_header_info.mime_type
234
+ set_content_type new_header_info.mime_type, charset
235
+ end
236
+
237
+ # Sets the HTTP response's content MIME type. For example, in the controller
238
+ # you could write this:
239
+ #
240
+ # response.content_type = "text/plain"
241
+ #
242
+ # If a character set has been defined for this response (see charset=) then
243
+ # the character set information will also be included in the content type
244
+ # information.
245
+
246
+ def content_type
247
+ parsed_content_type_header.mime_type
248
+ end
249
+
250
+ def sending_file=(v)
251
+ if true == v
252
+ self.charset = false
253
+ end
254
+ end
255
+
256
+ # Sets the HTTP character set. In case of +nil+ parameter
257
+ # it sets the charset to +default_charset+.
258
+ #
259
+ # response.charset = 'utf-16' # => 'utf-16'
260
+ # response.charset = nil # => 'utf-8'
261
+ def charset=(charset)
262
+ content_type = parsed_content_type_header.mime_type
263
+ if false == charset
264
+ set_content_type content_type, nil
265
+ else
266
+ set_content_type content_type, charset || self.class.default_charset
267
+ end
268
+ end
269
+
270
+ # The charset of the response. HTML wants to know the encoding of the
271
+ # content you're giving them, so we need to send that along.
272
+ def charset
273
+ header_info = parsed_content_type_header
274
+ header_info.charset || self.class.default_charset
191
275
  end
192
276
 
193
277
  # The response code of the request.
@@ -216,17 +300,15 @@ module ActionDispatch # :nodoc:
216
300
  # Returns the content of the response as a string. This contains the contents
217
301
  # of any calls to <tt>render</tt>.
218
302
  def body
219
- strings = []
220
- each { |part| strings << part.to_s }
221
- strings.join
303
+ @stream.body
222
304
  end
223
305
 
224
- EMPTY = " "
306
+ def write(string)
307
+ @stream.write string
308
+ end
225
309
 
226
310
  # Allows you to manually set or override the response body.
227
311
  def body=(body)
228
- @blank = true if body == EMPTY
229
-
230
312
  if body.respond_to?(:to_path)
231
313
  @stream = body
232
314
  else
@@ -236,31 +318,49 @@ module ActionDispatch # :nodoc:
236
318
  end
237
319
  end
238
320
 
239
- def body_parts
240
- parts = []
241
- @stream.each { |x| parts << x }
242
- parts
243
- end
321
+ # Avoid having to pass an open file handle as the response body.
322
+ # Rack::Sendfile will usually intercept the response and uses
323
+ # the path directly, so there is no reason to open the file.
324
+ class FileBody #:nodoc:
325
+ attr_reader :to_path
244
326
 
245
- def set_cookie(key, value)
246
- ::Rack::Utils.set_cookie_header!(header, key, value)
327
+ def initialize(path)
328
+ @to_path = path
329
+ end
330
+
331
+ def body
332
+ File.binread(to_path)
333
+ end
334
+
335
+ # Stream the file's contents if Rack::Sendfile isn't present.
336
+ def each
337
+ File.open(to_path, "rb") do |file|
338
+ while chunk = file.read(16384)
339
+ yield chunk
340
+ end
341
+ end
342
+ end
247
343
  end
248
344
 
249
- def delete_cookie(key, value={})
250
- ::Rack::Utils.delete_cookie_header!(header, key, value)
345
+ # Send the file stored at +path+ as the response body.
346
+ def send_file(path)
347
+ commit!
348
+ @stream = FileBody.new(path)
251
349
  end
252
350
 
253
- # The location header we'll be responding with.
254
- def location
255
- headers[LOCATION]
351
+ def reset_body!
352
+ @stream = build_buffer(self, [])
256
353
  end
257
- alias_method :redirect_url, :location
258
354
 
259
- # Sets the location header we'll be responding with.
260
- def location=(url)
261
- headers[LOCATION] = url
355
+ def body_parts
356
+ parts = []
357
+ @stream.each { |x| parts << x }
358
+ parts
262
359
  end
263
360
 
361
+ # The location header we'll be responding with.
362
+ alias_method :redirect_url, :location
363
+
264
364
  def close
265
365
  stream.close if stream.respond_to?(:close)
266
366
  end
@@ -277,37 +377,24 @@ module ActionDispatch # :nodoc:
277
377
  end
278
378
 
279
379
  # Turns the Response into a Rack-compatible array of the status, headers,
280
- # and body. Allows explict splatting:
380
+ # and body. Allows explicit splatting:
281
381
  #
282
382
  # status, headers, body = *response
283
383
  def to_a
384
+ commit!
284
385
  rack_response @status, @header.to_hash
285
386
  end
286
387
  alias prepare! to_a
287
388
 
288
- # Be super clear that a response object is not an Array. Defining this
289
- # would make implicit splatting work, but it also makes adding responses
290
- # as arrays work, and "flattening" responses, cascading to the rack body!
291
- # Not sensible behavior.
292
- def to_ary
293
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
294
- `ActionDispatch::Response#to_ary` no longer performs implicit conversion
295
- to an array. Please use `response.to_a` instead, or a splat like `status,
296
- headers, body = *response`.
297
- MSG
298
-
299
- to_a
300
- end
301
-
302
389
  # Returns the response cookies, converted to a Hash of (name => value) pairs
303
390
  #
304
391
  # assert_equal 'AuthorOfNewPage', r.cookies['author']
305
392
  def cookies
306
393
  cookies = {}
307
- if header = self[SET_COOKIE]
394
+ if header = get_header(SET_COOKIE)
308
395
  header = header.split("\n") if header.respond_to?(:to_str)
309
396
  header.each do |cookie|
310
- if pair = cookie.split(';').first
397
+ if pair = cookie.split(";").first
311
398
  key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) }
312
399
  cookies[key] = value
313
400
  end
@@ -318,14 +405,49 @@ module ActionDispatch # :nodoc:
318
405
 
319
406
  private
320
407
 
408
+ ContentTypeHeader = Struct.new :mime_type, :charset
409
+ NullContentTypeHeader = ContentTypeHeader.new nil, nil
410
+
411
+ def parse_content_type(content_type)
412
+ if content_type
413
+ type, charset = content_type.split(/;\s*charset=/)
414
+ type = nil if type && type.empty?
415
+ ContentTypeHeader.new(type, charset)
416
+ else
417
+ NullContentTypeHeader
418
+ end
419
+ end
420
+
421
+ # Small internal convenience method to get the parsed version of the current
422
+ # content type header.
423
+ def parsed_content_type_header
424
+ parse_content_type(get_header(CONTENT_TYPE))
425
+ end
426
+
427
+ def set_content_type(content_type, charset)
428
+ type = (content_type || "").dup
429
+ type << "; charset=#{charset.to_s.downcase}" if charset
430
+ set_header CONTENT_TYPE, type
431
+ end
432
+
321
433
  def before_committed
434
+ return if committed?
435
+ assign_default_content_type_and_charset!
436
+ merge_and_normalize_cache_control!(@cache_control)
437
+ handle_conditional_get!
438
+ handle_no_content!
322
439
  end
323
440
 
324
441
  def before_sending
325
- end
442
+ # Normally we've already committed by now, but it's possible
443
+ # (e.g., if the controller action tries to read back its own
444
+ # response) to get here before that. In that case, we must force
445
+ # an "early" commit: we're about to freeze the headers, so this is
446
+ # our last chance.
447
+ commit! unless committed?
326
448
 
327
- def merge_default_headers(original, default)
328
- default.respond_to?(:merge) ? default.merge(original) : original
449
+ headers.freeze
450
+ request.commit_cookie_jar! unless committed?
329
451
  end
330
452
 
331
453
  def build_buffer(response, body)
@@ -336,20 +458,12 @@ module ActionDispatch # :nodoc:
336
458
  body.respond_to?(:each) ? body : [body]
337
459
  end
338
460
 
339
- def assign_default_content_type_and_charset!(headers)
340
- return if headers[CONTENT_TYPE].present?
341
-
342
- @content_type ||= Mime::HTML
343
- @charset ||= self.class.default_charset unless @charset == false
461
+ def assign_default_content_type_and_charset!
462
+ return if content_type
344
463
 
345
- type = @content_type.to_s.dup
346
- type << "; charset=#{@charset}" if append_charset?
347
-
348
- headers[CONTENT_TYPE] = type
349
- end
350
-
351
- def append_charset?
352
- !@sending_file && @charset != false
464
+ ct = parsed_content_type_header
465
+ set_content_type(ct.mime_type || Mime[:html].to_s,
466
+ ct.charset || self.class.default_charset)
353
467
  end
354
468
 
355
469
  class RackBody
@@ -372,7 +486,7 @@ module ActionDispatch # :nodoc:
372
486
  end
373
487
 
374
488
  def respond_to?(method, include_private = false)
375
- if method.to_s == 'to_path'
489
+ if method.to_s == "to_path"
376
490
  @response.stream.respond_to?(method)
377
491
  else
378
492
  super
@@ -388,14 +502,15 @@ module ActionDispatch # :nodoc:
388
502
  end
389
503
  end
390
504
 
391
- def rack_response(status, header)
392
- assign_default_content_type_and_charset!(header)
393
- handle_conditional_get!
394
-
395
- header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
396
-
505
+ def handle_no_content!
397
506
  if NO_CONTENT_CODES.include?(@status)
398
- header.delete CONTENT_TYPE
507
+ @header.delete CONTENT_TYPE
508
+ @header.delete "Content-Length"
509
+ end
510
+ end
511
+
512
+ def rack_response(status, header)
513
+ if NO_CONTENT_CODES.include?(status)
399
514
  [status, header, []]
400
515
  else
401
516
  [status, header, RackBody.new(self)]