actionpack 4.2.10 → 7.2.0.rc1

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 (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +425 -497
  65. data/lib/action_controller.rb +44 -22
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,79 +1,98 @@
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
+ # :markup: markdown
4
+
5
+ require "active_support/core_ext/module/attribute_accessors"
6
+ require "action_dispatch/http/filter_redirect"
7
+ require "action_dispatch/http/cache"
8
+ require "monitor"
6
9
 
7
10
  module ActionDispatch # :nodoc:
11
+ # # Action Dispatch Response
12
+ #
8
13
  # Represents an HTTP response generated by a controller action. Use it to
9
14
  # retrieve the current state of the response, or customize the response. It can
10
- # either represent a real HTTP response (i.e. one that is meant to be sent
11
- # back to the web browser) or a TestResponse (i.e. one that is generated
12
- # from integration tests).
15
+ # either represent a real HTTP response (i.e. one that is meant to be sent back
16
+ # to the web browser) or a TestResponse (i.e. one that is generated from
17
+ # integration tests).
13
18
  #
14
- # \Response is mostly a Ruby on \Rails framework implementation detail, and
15
- # should never be used directly in controllers. Controllers should use the
16
- # methods defined in ActionController::Base instead. For example, if you want
17
- # to set the HTTP response's content MIME type, then use
18
- # ActionControllerBase#headers instead of Response#headers.
19
+ # The Response object for the current request is exposed on controllers as
20
+ # ActionController::Metal#response. ActionController::Metal also provides a few
21
+ # additional methods that delegate to attributes of the Response such as
22
+ # ActionController::Metal#headers.
19
23
  #
20
- # Nevertheless, integration tests may want to inspect controller responses in
21
- # more detail, and that's when \Response can be useful for application
22
- # developers. Integration test methods such as
23
- # ActionDispatch::Integration::Session#get and
24
- # ActionDispatch::Integration::Session#post return objects of type
25
- # TestResponse (which are of course also of type \Response).
24
+ # Integration tests will likely also want to inspect responses in more detail.
25
+ # Methods such as Integration::RequestHelpers#get and
26
+ # Integration::RequestHelpers#post return instances of TestResponse (which
27
+ # inherits from Response) for this purpose.
26
28
  #
27
29
  # For example, the following demo integration test prints the body of the
28
30
  # controller response to the console:
29
31
  #
30
- # class DemoControllerTest < ActionDispatch::IntegrationTest
31
- # def test_print_root_path_to_console
32
- # get('/')
33
- # puts response.body
34
- # end
35
- # end
32
+ # class DemoControllerTest < ActionDispatch::IntegrationTest
33
+ # def test_print_root_path_to_console
34
+ # get('/')
35
+ # puts response.body
36
+ # end
37
+ # end
36
38
  class Response
39
+ begin
40
+ # For `Rack::Headers` (Rack 3+):
41
+ require "rack/headers"
42
+ Headers = ::Rack::Headers
43
+ rescue LoadError
44
+ # For `Rack::Utils::HeaderHash`:
45
+ require "rack/utils"
46
+ Headers = ::Rack::Utils::HeaderHash
47
+ end
48
+
49
+ # To be deprecated:
50
+ Header = Headers
51
+
37
52
  # The request that the response is responding to.
38
53
  attr_accessor :request
39
54
 
40
55
  # The HTTP status code.
41
56
  attr_reader :status
42
57
 
43
- attr_writer :sending_file
44
-
45
- # Get and set headers for this response.
46
- attr_accessor :header
47
-
48
- alias_method :headers=, :header=
49
- alias_method :headers, :header
50
-
51
- delegate :[], :[]=, :to => :@header
52
- delegate :each, :to => :@stream
53
-
54
- # Sets the HTTP response's content MIME type. For example, in the controller
55
- # you could write this:
58
+ # The headers for the response.
56
59
  #
57
- # response.content_type = "text/plain"
60
+ # header["Content-Type"] # => "text/plain"
61
+ # header["Content-Type"] = "application/json"
62
+ # header["Content-Type"] # => "application/json"
58
63
  #
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
64
+ # Also aliased as `headers`.
65
+ #
66
+ # headers["Content-Type"] # => "text/plain"
67
+ # headers["Content-Type"] = "application/json"
68
+ # headers["Content-Type"] # => "application/json"
69
+ #
70
+ # Also aliased as `header` for compatibility.
71
+ attr_reader :headers
72
+
73
+ alias_method :header, :headers
63
74
 
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
75
+ delegate :[], :[]=, to: :@headers
76
+
77
+ def each(&block)
78
+ sending!
79
+ x = @stream.each(&block)
80
+ sent!
81
+ x
82
+ end
67
83
 
68
- CONTENT_TYPE = "Content-Type".freeze
69
- SET_COOKIE = "Set-Cookie".freeze
70
- LOCATION = "Location".freeze
71
- NO_CONTENT_CODES = [204, 304]
84
+ CONTENT_TYPE = "Content-Type"
85
+ SET_COOKIE = "Set-Cookie"
86
+ NO_CONTENT_CODES = [100, 101, 102, 103, 204, 205, 304]
72
87
 
73
- cattr_accessor(:default_charset) { "utf-8" }
74
- cattr_accessor(:default_headers)
88
+ cattr_accessor :default_charset, default: "utf-8"
89
+ cattr_accessor :default_headers
75
90
 
76
91
  include Rack::Response::Helpers
92
+ # Aliasing these off because AD::Http::Cache::Response defines them.
93
+ alias :_cache_control :cache_control
94
+ alias :_cache_control= :cache_control=
95
+
77
96
  include ActionDispatch::Http::FilterRedirect
78
97
  include ActionDispatch::Http::Cache::Response
79
98
  include MonitorMixin
@@ -83,20 +102,40 @@ module ActionDispatch # :nodoc:
83
102
  @response = response
84
103
  @buf = buf
85
104
  @closed = false
105
+ @str_body = nil
106
+ end
107
+
108
+ def to_ary
109
+ @buf.respond_to?(:to_ary) ?
110
+ @buf.to_ary :
111
+ @buf.each
112
+ end
113
+
114
+ def body
115
+ @str_body ||= begin
116
+ buf = +""
117
+ each { |chunk| buf << chunk }
118
+ buf
119
+ end
86
120
  end
87
121
 
88
122
  def write(string)
89
123
  raise IOError, "closed stream" if closed?
90
124
 
125
+ @str_body = nil
91
126
  @response.commit!
92
127
  @buf.push string
93
128
  end
129
+ alias_method :<<, :write
94
130
 
95
131
  def each(&block)
96
- @response.sending!
97
- x = @buf.each(&block)
98
- @response.sent!
99
- x
132
+ if @str_body
133
+ return enum_for(:each) unless block_given?
134
+
135
+ yield @str_body
136
+ else
137
+ each_chunk(&block)
138
+ end
100
139
  end
101
140
 
102
141
  def abort
@@ -110,39 +149,51 @@ module ActionDispatch # :nodoc:
110
149
  def closed?
111
150
  @closed
112
151
  end
152
+
153
+ private
154
+ def each_chunk(&block)
155
+ @buf.each(&block)
156
+ end
157
+ end
158
+
159
+ def self.create(status = 200, headers = {}, body = [], default_headers: self.default_headers)
160
+ headers = merge_default_headers(headers, default_headers)
161
+ new status, headers, body
162
+ end
163
+
164
+ def self.merge_default_headers(original, default)
165
+ default.respond_to?(:merge) ? default.merge(original) : original
113
166
  end
114
167
 
115
168
  # The underlying body, as a streamable object.
116
169
  attr_reader :stream
117
170
 
118
- def initialize(status = 200, header = {}, body = [], options = {})
171
+ def initialize(status = 200, headers = nil, body = [])
119
172
  super()
120
173
 
121
- default_headers = options.fetch(:default_headers, self.class.default_headers)
122
- header = merge_default_headers(header, default_headers)
174
+ @headers = Headers.new
175
+
176
+ headers&.each do |key, value|
177
+ @headers[key] = value
178
+ end
123
179
 
124
- self.body, self.header, self.status = body, header, status
180
+ self.body, self.status = body, status
125
181
 
126
- @sending_file = false
127
- @blank = false
128
182
  @cv = new_cond
129
183
  @committed = false
130
184
  @sending = false
131
185
  @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
186
 
141
187
  prepare_cache_control!
142
188
 
143
189
  yield self if block_given?
144
190
  end
145
191
 
192
+ def has_header?(key); @headers.key? key; end
193
+ def get_header(key); @headers[key]; end
194
+ def set_header(key, v); @headers[key] = v; end
195
+ def delete_header(key); @headers.delete key; end
196
+
146
197
  def await_commit
147
198
  synchronize do
148
199
  @cv.wait_until { @committed }
@@ -180,14 +231,75 @@ module ActionDispatch # :nodoc:
180
231
  def committed?; synchronize { @committed }; end
181
232
  def sent?; synchronize { @sent }; end
182
233
 
234
+ ##
235
+ # :method: location
236
+ #
237
+ # Location of the response.
238
+
239
+ ##
240
+ # :method: location=
241
+ #
242
+ # :call-seq: location=(location)
243
+ #
244
+ # Sets the location of the response
245
+
183
246
  # Sets the HTTP status code.
184
247
  def status=(status)
185
248
  @status = Rack::Utils.status_code(status)
186
249
  end
187
250
 
188
- # Sets the HTTP content type.
251
+ # Sets the HTTP response's content MIME type. For example, in the controller you
252
+ # could write this:
253
+ #
254
+ # response.content_type = "text/plain"
255
+ #
256
+ # If a character set has been defined for this response (see #charset=) then the
257
+ # character set information will also be included in the content type
258
+ # information.
189
259
  def content_type=(content_type)
190
- @content_type = content_type.to_s
260
+ return unless content_type
261
+ new_header_info = parse_content_type(content_type.to_s)
262
+ prev_header_info = parsed_content_type_header
263
+ charset = new_header_info.charset || prev_header_info.charset
264
+ charset ||= self.class.default_charset unless prev_header_info.mime_type
265
+ set_content_type new_header_info.mime_type, charset
266
+ end
267
+
268
+ # Content type of response.
269
+ def content_type
270
+ super.presence
271
+ end
272
+
273
+ # Media type of response.
274
+ def media_type
275
+ parsed_content_type_header.mime_type
276
+ end
277
+
278
+ def sending_file=(v)
279
+ if true == v
280
+ self.charset = false
281
+ end
282
+ end
283
+
284
+ # Sets the HTTP character set. In case of `nil` parameter it sets the charset to
285
+ # `default_charset`.
286
+ #
287
+ # response.charset = 'utf-16' # => 'utf-16'
288
+ # response.charset = nil # => 'utf-8'
289
+ def charset=(charset)
290
+ content_type = parsed_content_type_header.mime_type
291
+ if false == charset
292
+ set_content_type content_type, nil
293
+ else
294
+ set_content_type content_type, charset || self.class.default_charset
295
+ end
296
+ end
297
+
298
+ # The charset of the response. HTML wants to know the encoding of the content
299
+ # you're giving them, so we need to send that along.
300
+ def charset
301
+ header_info = parsed_content_type_header
302
+ header_info.charset || self.class.default_charset
191
303
  end
192
304
 
193
305
  # The response code of the request.
@@ -195,38 +307,36 @@ module ActionDispatch # :nodoc:
195
307
  @status
196
308
  end
197
309
 
198
- # Returns a string to ensure compatibility with <tt>Net::HTTPResponse</tt>.
310
+ # Returns a string to ensure compatibility with `Net::HTTPResponse`.
199
311
  def code
200
312
  @status.to_s
201
313
  end
202
314
 
203
315
  # Returns the corresponding message for the current HTTP status code:
204
316
  #
205
- # response.status = 200
206
- # response.message # => "OK"
317
+ # response.status = 200
318
+ # response.message # => "OK"
207
319
  #
208
- # response.status = 404
209
- # response.message # => "Not Found"
320
+ # response.status = 404
321
+ # response.message # => "Not Found"
210
322
  #
211
323
  def message
212
324
  Rack::Utils::HTTP_STATUS_CODES[@status]
213
325
  end
214
326
  alias_method :status_message, :message
215
327
 
216
- # Returns the content of the response as a string. This contains the contents
217
- # of any calls to <tt>render</tt>.
328
+ # Returns the content of the response as a string. This contains the contents of
329
+ # any calls to `render`.
218
330
  def body
219
- strings = []
220
- each { |part| strings << part.to_s }
221
- strings.join
331
+ @stream.body
222
332
  end
223
333
 
224
- EMPTY = " "
334
+ def write(string)
335
+ @stream.write string
336
+ end
225
337
 
226
338
  # Allows you to manually set or override the response body.
227
339
  def body=(body)
228
- @blank = true if body == EMPTY
229
-
230
340
  if body.respond_to?(:to_path)
231
341
  @stream = body
232
342
  else
@@ -236,31 +346,49 @@ module ActionDispatch # :nodoc:
236
346
  end
237
347
  end
238
348
 
239
- def body_parts
240
- parts = []
241
- @stream.each { |x| parts << x }
242
- parts
243
- end
349
+ # Avoid having to pass an open file handle as the response body. Rack::Sendfile
350
+ # will usually intercept the response and uses the path directly, so there is no
351
+ # reason to open the file.
352
+ class FileBody # :nodoc:
353
+ attr_reader :to_path
354
+
355
+ def initialize(path)
356
+ @to_path = path
357
+ end
244
358
 
245
- def set_cookie(key, value)
246
- ::Rack::Utils.set_cookie_header!(header, key, value)
359
+ def body
360
+ File.binread(to_path)
361
+ end
362
+
363
+ # Stream the file's contents if Rack::Sendfile isn't present.
364
+ def each
365
+ File.open(to_path, "rb") do |file|
366
+ while chunk = file.read(16384)
367
+ yield chunk
368
+ end
369
+ end
370
+ end
247
371
  end
248
372
 
249
- def delete_cookie(key, value={})
250
- ::Rack::Utils.delete_cookie_header!(header, key, value)
373
+ # Send the file stored at `path` as the response body.
374
+ def send_file(path)
375
+ commit!
376
+ @stream = FileBody.new(path)
251
377
  end
252
378
 
253
- # The location header we'll be responding with.
254
- def location
255
- headers[LOCATION]
379
+ def reset_body!
380
+ @stream = build_buffer(self, [])
256
381
  end
257
- alias_method :redirect_url, :location
258
382
 
259
- # Sets the location header we'll be responding with.
260
- def location=(url)
261
- headers[LOCATION] = url
383
+ def body_parts
384
+ parts = []
385
+ @stream.each { |x| parts << x }
386
+ parts
262
387
  end
263
388
 
389
+ # The location header we'll be responding with.
390
+ alias_method :redirect_url, :location
391
+
264
392
  def close
265
393
  stream.close if stream.respond_to?(:close)
266
394
  end
@@ -269,45 +397,31 @@ module ActionDispatch # :nodoc:
269
397
  if stream.respond_to?(:abort)
270
398
  stream.abort
271
399
  elsif stream.respond_to?(:close)
272
- # `stream.close` should really be reserved for a close from the
273
- # other direction, but we must fall back to it for
274
- # compatibility.
400
+ # `stream.close` should really be reserved for a close from the other direction,
401
+ # but we must fall back to it for compatibility.
275
402
  stream.close
276
403
  end
277
404
  end
278
405
 
279
- # Turns the Response into a Rack-compatible array of the status, headers,
280
- # and body. Allows explict splatting:
406
+ # Turns the Response into a Rack-compatible array of the status, headers, and
407
+ # body. Allows explicit splatting:
281
408
  #
282
- # status, headers, body = *response
409
+ # status, headers, body = *response
283
410
  def to_a
284
- rack_response @status, @header.to_hash
411
+ commit!
412
+ rack_response @status, @headers.to_hash
285
413
  end
286
414
  alias prepare! to_a
287
415
 
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
416
  # Returns the response cookies, converted to a Hash of (name => value) pairs
303
417
  #
304
- # assert_equal 'AuthorOfNewPage', r.cookies['author']
418
+ # assert_equal 'AuthorOfNewPage', r.cookies['author']
305
419
  def cookies
306
420
  cookies = {}
307
- if header = self[SET_COOKIE]
421
+ if header = get_header(SET_COOKIE)
308
422
  header = header.split("\n") if header.respond_to?(:to_str)
309
423
  header.each do |cookie|
310
- if pair = cookie.split(';').first
424
+ if pair = cookie.split(";").first
311
425
  key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) }
312
426
  cookies[key] = value
313
427
  end
@@ -317,15 +431,51 @@ module ActionDispatch # :nodoc:
317
431
  end
318
432
 
319
433
  private
434
+ ContentTypeHeader = Struct.new :mime_type, :charset
435
+ NullContentTypeHeader = ContentTypeHeader.new nil, nil
436
+
437
+ CONTENT_TYPE_PARSER = /
438
+ \A
439
+ (?<mime_type>[^;\s]+\s*(?:;\s*(?:(?!charset)[^;\s])+)*)?
440
+ (?:;\s*charset=(?<quote>"?)(?<charset>[^;\s]+)\k<quote>)?
441
+ /x # :nodoc:
442
+
443
+ def parse_content_type(content_type)
444
+ if content_type && match = CONTENT_TYPE_PARSER.match(content_type)
445
+ ContentTypeHeader.new(match[:mime_type], match[:charset])
446
+ else
447
+ NullContentTypeHeader
448
+ end
449
+ end
450
+
451
+ # Small internal convenience method to get the parsed version of the current
452
+ # content type header.
453
+ def parsed_content_type_header
454
+ parse_content_type(get_header(CONTENT_TYPE))
455
+ end
456
+
457
+ def set_content_type(content_type, charset)
458
+ type = content_type || ""
459
+ type = "#{type}; charset=#{charset.to_s.downcase}" if charset
460
+ set_header CONTENT_TYPE, type
461
+ end
320
462
 
321
463
  def before_committed
464
+ return if committed?
465
+ assign_default_content_type_and_charset!
466
+ merge_and_normalize_cache_control!(@cache_control)
467
+ handle_conditional_get!
468
+ handle_no_content!
322
469
  end
323
470
 
324
471
  def before_sending
325
- end
472
+ # Normally we've already committed by now, but it's possible (e.g., if the
473
+ # controller action tries to read back its own response) to get here before
474
+ # that. In that case, we must force an "early" commit: we're about to freeze the
475
+ # headers, so this is our last chance.
476
+ commit! unless committed?
326
477
 
327
- def merge_default_headers(original, default)
328
- default.respond_to?(:merge) ? default.merge(original) : original
478
+ @request.commit_cookie_jar! unless committed?
329
479
  end
330
480
 
331
481
  def build_buffer(response, body)
@@ -336,20 +486,12 @@ module ActionDispatch # :nodoc:
336
486
  body.respond_to?(:each) ? body : [body]
337
487
  end
338
488
 
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
344
-
345
- type = @content_type.to_s.dup
346
- type << "; charset=#{@charset}" if append_charset?
347
-
348
- headers[CONTENT_TYPE] = type
349
- end
489
+ def assign_default_content_type_and_charset!
490
+ return if media_type
350
491
 
351
- def append_charset?
352
- !@sending_file && @charset != false
492
+ ct = parsed_content_type_header
493
+ set_content_type(ct.mime_type || Mime[:html].to_s,
494
+ ct.charset || self.class.default_charset)
353
495
  end
354
496
 
355
497
  class RackBody
@@ -357,13 +499,9 @@ module ActionDispatch # :nodoc:
357
499
  @response = response
358
500
  end
359
501
 
360
- def each(*args, &block)
361
- @response.each(*args, &block)
362
- end
363
-
364
502
  def close
365
- # Rack "close" maps to Response#abort, and *not* Response#close
366
- # (which is used when the controller's finished writing)
503
+ # Rack "close" maps to Response#abort, and **not** Response#close (which is used
504
+ # when the controller's finished writing)
367
505
  @response.abort
368
506
  end
369
507
 
@@ -371,35 +509,48 @@ module ActionDispatch # :nodoc:
371
509
  @response.body
372
510
  end
373
511
 
512
+ BODY_METHODS = { to_ary: true, each: true, call: true, to_path: true }
513
+
374
514
  def respond_to?(method, include_private = false)
375
- if method.to_s == 'to_path'
515
+ if BODY_METHODS.key?(method)
376
516
  @response.stream.respond_to?(method)
377
517
  else
378
518
  super
379
519
  end
380
520
  end
381
521
 
382
- def to_path
383
- @response.stream.to_path
522
+ def to_ary
523
+ @response.stream.to_ary
384
524
  end
385
525
 
386
- def to_ary
387
- nil
526
+ def each(*args, &block)
527
+ @response.each(*args, &block)
388
528
  end
389
- end
390
529
 
391
- def rack_response(status, header)
392
- assign_default_content_type_and_charset!(header)
393
- handle_conditional_get!
530
+ def call(*arguments, &block)
531
+ @response.stream.call(*arguments, &block)
532
+ end
394
533
 
395
- header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
534
+ def to_path
535
+ @response.stream.to_path
536
+ end
537
+ end
396
538
 
539
+ def handle_no_content!
397
540
  if NO_CONTENT_CODES.include?(@status)
398
- header.delete CONTENT_TYPE
399
- [status, header, []]
541
+ @headers.delete CONTENT_TYPE
542
+ @headers.delete "Content-Length"
543
+ end
544
+ end
545
+
546
+ def rack_response(status, headers)
547
+ if NO_CONTENT_CODES.include?(status)
548
+ [status, headers, []]
400
549
  else
401
- [status, header, RackBody.new(self)]
550
+ [status, headers, RackBody.new(self)]
402
551
  end
403
552
  end
404
553
  end
554
+
555
+ ActiveSupport.run_load_hooks(:action_dispatch_response, Response)
405
556
  end