omg-actionpack 8.0.0.alpha1

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 (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  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 +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  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 +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  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 +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  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 +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  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 +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  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 +691 -0
  65. data/lib/action_controller.rb +80 -0
  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 +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  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 +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  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 +284 -0
  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 +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,556 @@
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"
9
+
10
+ module ActionDispatch # :nodoc:
11
+ # # Action Dispatch Response
12
+ #
13
+ # Represents an HTTP response generated by a controller action. Use it to
14
+ # retrieve the current state of the response, or customize the response. It can
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).
18
+ #
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.
23
+ #
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.
28
+ #
29
+ # For example, the following demo integration test prints the body of the
30
+ # controller response to the console:
31
+ #
32
+ # class DemoControllerTest < ActionDispatch::IntegrationTest
33
+ # def test_print_root_path_to_console
34
+ # get('/')
35
+ # puts response.body
36
+ # end
37
+ # end
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
+
52
+ # The request that the response is responding to.
53
+ attr_accessor :request
54
+
55
+ # The HTTP status code.
56
+ attr_reader :status
57
+
58
+ # The headers for the response.
59
+ #
60
+ # header["Content-Type"] # => "text/plain"
61
+ # header["Content-Type"] = "application/json"
62
+ # header["Content-Type"] # => "application/json"
63
+ #
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
74
+
75
+ delegate :[], :[]=, to: :@headers
76
+
77
+ def each(&block)
78
+ sending!
79
+ x = @stream.each(&block)
80
+ sent!
81
+ x
82
+ end
83
+
84
+ CONTENT_TYPE = "Content-Type"
85
+ SET_COOKIE = "Set-Cookie"
86
+ NO_CONTENT_CODES = [100, 101, 102, 103, 204, 205, 304]
87
+
88
+ cattr_accessor :default_charset, default: "utf-8"
89
+ cattr_accessor :default_headers
90
+
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
+
96
+ include ActionDispatch::Http::FilterRedirect
97
+ include ActionDispatch::Http::Cache::Response
98
+ include MonitorMixin
99
+
100
+ class Buffer # :nodoc:
101
+ def initialize(response, buf)
102
+ @response = response
103
+ @buf = buf
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
120
+ end
121
+
122
+ def write(string)
123
+ raise IOError, "closed stream" if closed?
124
+
125
+ @str_body = nil
126
+ @response.commit!
127
+ @buf.push string
128
+ end
129
+ alias_method :<<, :write
130
+
131
+ def each(&block)
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
139
+ end
140
+
141
+ def abort
142
+ end
143
+
144
+ def close
145
+ @response.commit!
146
+ @closed = true
147
+ end
148
+
149
+ def closed?
150
+ @closed
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
166
+ end
167
+
168
+ # The underlying body, as a streamable object.
169
+ attr_reader :stream
170
+
171
+ def initialize(status = 200, headers = nil, body = [])
172
+ super()
173
+
174
+ @headers = Headers.new
175
+
176
+ headers&.each do |key, value|
177
+ @headers[key] = value
178
+ end
179
+
180
+ self.body, self.status = body, status
181
+
182
+ @cv = new_cond
183
+ @committed = false
184
+ @sending = false
185
+ @sent = false
186
+
187
+ prepare_cache_control!
188
+
189
+ yield self if block_given?
190
+ end
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
+
197
+ def await_commit
198
+ synchronize do
199
+ @cv.wait_until { @committed }
200
+ end
201
+ end
202
+
203
+ def await_sent
204
+ synchronize { @cv.wait_until { @sent } }
205
+ end
206
+
207
+ def commit!
208
+ synchronize do
209
+ before_committed
210
+ @committed = true
211
+ @cv.broadcast
212
+ end
213
+ end
214
+
215
+ def sending!
216
+ synchronize do
217
+ before_sending
218
+ @sending = true
219
+ @cv.broadcast
220
+ end
221
+ end
222
+
223
+ def sent!
224
+ synchronize do
225
+ @sent = true
226
+ @cv.broadcast
227
+ end
228
+ end
229
+
230
+ def sending?; synchronize { @sending }; end
231
+ def committed?; synchronize { @committed }; end
232
+ def sent?; synchronize { @sent }; end
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
+
246
+ # Sets the HTTP status code.
247
+ def status=(status)
248
+ @status = Rack::Utils.status_code(status)
249
+ end
250
+
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.
259
+ def content_type=(content_type)
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
303
+ end
304
+
305
+ # The response code of the request.
306
+ def response_code
307
+ @status
308
+ end
309
+
310
+ # Returns a string to ensure compatibility with `Net::HTTPResponse`.
311
+ def code
312
+ @status.to_s
313
+ end
314
+
315
+ # Returns the corresponding message for the current HTTP status code:
316
+ #
317
+ # response.status = 200
318
+ # response.message # => "OK"
319
+ #
320
+ # response.status = 404
321
+ # response.message # => "Not Found"
322
+ #
323
+ def message
324
+ Rack::Utils::HTTP_STATUS_CODES[@status]
325
+ end
326
+ alias_method :status_message, :message
327
+
328
+ # Returns the content of the response as a string. This contains the contents of
329
+ # any calls to `render`.
330
+ def body
331
+ @stream.body
332
+ end
333
+
334
+ def write(string)
335
+ @stream.write string
336
+ end
337
+
338
+ # Allows you to manually set or override the response body.
339
+ 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)
345
+ end
346
+ end
347
+ end
348
+
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
358
+
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
371
+ end
372
+
373
+ # Send the file stored at `path` as the response body.
374
+ def send_file(path)
375
+ commit!
376
+ @stream = FileBody.new(path)
377
+ end
378
+
379
+ def reset_body!
380
+ @stream = build_buffer(self, [])
381
+ end
382
+
383
+ def body_parts
384
+ parts = []
385
+ @stream.each { |x| parts << x }
386
+ parts
387
+ end
388
+
389
+ # The location header we'll be responding with.
390
+ alias_method :redirect_url, :location
391
+
392
+ def close
393
+ stream.close if stream.respond_to?(:close)
394
+ end
395
+
396
+ def abort
397
+ if stream.respond_to?(:abort)
398
+ stream.abort
399
+ elsif stream.respond_to?(:close)
400
+ # `stream.close` should really be reserved for a close from the other direction,
401
+ # but we must fall back to it for compatibility.
402
+ stream.close
403
+ end
404
+ end
405
+
406
+ # Turns the Response into a Rack-compatible array of the status, headers, and
407
+ # body. Allows explicit splatting:
408
+ #
409
+ # status, headers, body = *response
410
+ def to_a
411
+ commit!
412
+ rack_response @status, @headers.to_hash
413
+ end
414
+ alias prepare! to_a
415
+
416
+ # Returns the response cookies, converted to a Hash of (name => value) pairs
417
+ #
418
+ # assert_equal 'AuthorOfNewPage', r.cookies['author']
419
+ def cookies
420
+ cookies = {}
421
+ if header = get_header(SET_COOKIE)
422
+ header = header.split("\n") if header.respond_to?(:to_str)
423
+ header.each do |cookie|
424
+ if pair = cookie.split(";").first
425
+ key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) }
426
+ cookies[key] = value
427
+ end
428
+ end
429
+ end
430
+ cookies
431
+ end
432
+
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
462
+
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!
469
+ end
470
+
471
+ def before_sending
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?
477
+
478
+ @request.commit_cookie_jar! unless committed?
479
+ end
480
+
481
+ def build_buffer(response, body)
482
+ Buffer.new response, body
483
+ end
484
+
485
+ def munge_body_object(body)
486
+ body.respond_to?(:each) ? body : [body]
487
+ end
488
+
489
+ def assign_default_content_type_and_charset!
490
+ return if media_type
491
+
492
+ ct = parsed_content_type_header
493
+ set_content_type(ct.mime_type || Mime[:html].to_s,
494
+ ct.charset || self.class.default_charset)
495
+ end
496
+
497
+ class RackBody
498
+ def initialize(response)
499
+ @response = response
500
+ end
501
+
502
+ def close
503
+ # Rack "close" maps to Response#abort, and **not** Response#close (which is used
504
+ # when the controller's finished writing)
505
+ @response.abort
506
+ end
507
+
508
+ def body
509
+ @response.body
510
+ end
511
+
512
+ BODY_METHODS = { to_ary: true, each: true, call: true, to_path: true }
513
+
514
+ def respond_to?(method, include_private = false)
515
+ if BODY_METHODS.key?(method)
516
+ @response.stream.respond_to?(method)
517
+ else
518
+ super
519
+ end
520
+ end
521
+
522
+ def to_ary
523
+ @response.stream.to_ary
524
+ end
525
+
526
+ def each(*args, &block)
527
+ @response.each(*args, &block)
528
+ end
529
+
530
+ def call(*arguments, &block)
531
+ @response.stream.call(*arguments, &block)
532
+ end
533
+
534
+ def to_path
535
+ @response.stream.to_path
536
+ end
537
+ end
538
+
539
+ def handle_no_content!
540
+ if NO_CONTENT_CODES.include?(@status)
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, []]
549
+ else
550
+ [status, headers, RackBody.new(self)]
551
+ end
552
+ end
553
+ end
554
+
555
+ ActiveSupport.run_load_hooks(:action_dispatch_response, Response)
556
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionDispatch
6
+ module Http
7
+ # # Action Dispatch HTTP UploadedFile
8
+ #
9
+ # Models uploaded files.
10
+ #
11
+ # The actual file is accessible via the `tempfile` accessor, though some of its
12
+ # interface is available directly for convenience.
13
+ #
14
+ # Uploaded files are temporary files whose lifespan is one request. When the
15
+ # object is finalized Ruby unlinks the file, so there is no need to clean them
16
+ # with a separate maintenance task.
17
+ class UploadedFile
18
+ # The basename of the file in the client.
19
+ attr_accessor :original_filename
20
+
21
+ # A string with the MIME type of the file.
22
+ attr_accessor :content_type
23
+
24
+ # A `Tempfile` object with the actual uploaded file. Note that some of its
25
+ # interface is available directly.
26
+ attr_accessor :tempfile
27
+
28
+ # A string with the headers of the multipart request.
29
+ attr_accessor :headers
30
+
31
+ def initialize(hash) # :nodoc:
32
+ @tempfile = hash[:tempfile]
33
+ raise(ArgumentError, ":tempfile is required") unless @tempfile
34
+
35
+ @content_type = hash[:type]
36
+
37
+ if hash[:filename]
38
+ @original_filename = hash[:filename].dup
39
+
40
+ begin
41
+ @original_filename.encode!(Encoding::UTF_8)
42
+ rescue EncodingError
43
+ @original_filename.force_encoding(Encoding::UTF_8)
44
+ end
45
+ else
46
+ @original_filename = nil
47
+ end
48
+
49
+ if hash[:head]
50
+ @headers = hash[:head].dup
51
+
52
+ begin
53
+ @headers.encode!(Encoding::UTF_8)
54
+ rescue EncodingError
55
+ @headers.force_encoding(Encoding::UTF_8)
56
+ end
57
+ else
58
+ @headers = nil
59
+ end
60
+ end
61
+
62
+ # Shortcut for `tempfile.read`.
63
+ def read(length = nil, buffer = nil)
64
+ @tempfile.read(length, buffer)
65
+ end
66
+
67
+ # Shortcut for `tempfile.open`.
68
+ def open
69
+ @tempfile.open
70
+ end
71
+
72
+ # Shortcut for `tempfile.close`.
73
+ def close(unlink_now = false)
74
+ @tempfile.close(unlink_now)
75
+ end
76
+
77
+ # Shortcut for `tempfile.path`.
78
+ def path
79
+ @tempfile.path
80
+ end
81
+
82
+ # Shortcut for `tempfile.to_path`.
83
+ def to_path
84
+ @tempfile.to_path
85
+ end
86
+
87
+ # Shortcut for `tempfile.rewind`.
88
+ def rewind
89
+ @tempfile.rewind
90
+ end
91
+
92
+ # Shortcut for `tempfile.size`.
93
+ def size
94
+ @tempfile.size
95
+ end
96
+
97
+ # Shortcut for `tempfile.eof?`.
98
+ def eof?
99
+ @tempfile.eof?
100
+ end
101
+
102
+ def to_io
103
+ @tempfile.to_io
104
+ end
105
+ end
106
+ end
107
+ end