actionpack 4.2.11.3 → 5.0.0.beta1

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 (125) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +379 -462
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller.rb +0 -2
  6. data/lib/abstract_controller/base.rb +17 -32
  7. data/lib/abstract_controller/callbacks.rb +52 -19
  8. data/lib/abstract_controller/collector.rb +4 -9
  9. data/lib/abstract_controller/helpers.rb +2 -2
  10. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  11. data/lib/abstract_controller/rendering.rb +27 -22
  12. data/lib/abstract_controller/translation.rb +8 -7
  13. data/lib/action_controller.rb +4 -3
  14. data/lib/action_controller/api.rb +146 -0
  15. data/lib/action_controller/base.rb +6 -10
  16. data/lib/action_controller/caching.rb +1 -3
  17. data/lib/action_controller/caching/fragments.rb +48 -3
  18. data/lib/action_controller/form_builder.rb +48 -0
  19. data/lib/action_controller/log_subscriber.rb +1 -10
  20. data/lib/action_controller/metal.rb +89 -62
  21. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  22. data/lib/action_controller/metal/conditional_get.rb +65 -24
  23. data/lib/action_controller/metal/cookies.rb +0 -2
  24. data/lib/action_controller/metal/data_streaming.rb +2 -22
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  26. data/lib/action_controller/metal/exceptions.rb +11 -6
  27. data/lib/action_controller/metal/force_ssl.rb +6 -6
  28. data/lib/action_controller/metal/head.rb +14 -7
  29. data/lib/action_controller/metal/helpers.rb +9 -5
  30. data/lib/action_controller/metal/http_authentication.rb +37 -38
  31. data/lib/action_controller/metal/implicit_render.rb +23 -6
  32. data/lib/action_controller/metal/instrumentation.rb +0 -1
  33. data/lib/action_controller/metal/live.rb +17 -55
  34. data/lib/action_controller/metal/mime_responds.rb +17 -37
  35. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  36. data/lib/action_controller/metal/redirecting.rb +32 -9
  37. data/lib/action_controller/metal/renderers.rb +10 -8
  38. data/lib/action_controller/metal/rendering.rb +38 -6
  39. data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
  40. data/lib/action_controller/metal/rescue.rb +2 -4
  41. data/lib/action_controller/metal/streaming.rb +4 -4
  42. data/lib/action_controller/metal/strong_parameters.rb +231 -78
  43. data/lib/action_controller/metal/testing.rb +1 -12
  44. data/lib/action_controller/metal/url_for.rb +12 -5
  45. data/lib/action_controller/renderer.rb +111 -0
  46. data/lib/action_controller/template_assertions.rb +9 -0
  47. data/lib/action_controller/test_case.rb +267 -363
  48. data/lib/action_dispatch.rb +2 -1
  49. data/lib/action_dispatch/http/cache.rb +23 -26
  50. data/lib/action_dispatch/http/filter_parameters.rb +6 -8
  51. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  52. data/lib/action_dispatch/http/headers.rb +28 -11
  53. data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
  54. data/lib/action_dispatch/http/mime_type.rb +92 -61
  55. data/lib/action_dispatch/http/mime_types.rb +1 -4
  56. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  57. data/lib/action_dispatch/http/parameters.rb +45 -41
  58. data/lib/action_dispatch/http/request.rb +146 -82
  59. data/lib/action_dispatch/http/response.rb +180 -99
  60. data/lib/action_dispatch/http/url.rb +117 -8
  61. data/lib/action_dispatch/journey/formatter.rb +34 -28
  62. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  63. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  64. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  65. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  66. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  67. data/lib/action_dispatch/journey/path/pattern.rb +37 -41
  68. data/lib/action_dispatch/journey/route.rb +71 -17
  69. data/lib/action_dispatch/journey/router.rb +5 -6
  70. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  71. data/lib/action_dispatch/journey/routes.rb +14 -15
  72. data/lib/action_dispatch/journey/visitors.rb +86 -43
  73. data/lib/action_dispatch/middleware/cookies.rb +184 -135
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
  75. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
  76. data/lib/action_dispatch/middleware/flash.rb +61 -45
  77. data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
  78. data/lib/action_dispatch/middleware/params_parser.rb +30 -46
  79. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  80. data/lib/action_dispatch/middleware/reloader.rb +2 -4
  81. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  82. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  83. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  84. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  85. data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
  86. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  87. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  88. data/lib/action_dispatch/middleware/ssl.rb +93 -36
  89. data/lib/action_dispatch/middleware/stack.rb +43 -48
  90. data/lib/action_dispatch/middleware/static.rb +52 -40
  91. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  92. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  93. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  94. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  95. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  96. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  97. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  98. data/lib/action_dispatch/railtie.rb +0 -2
  99. data/lib/action_dispatch/request/session.rb +66 -34
  100. data/lib/action_dispatch/request/utils.rb +51 -19
  101. data/lib/action_dispatch/routing.rb +3 -8
  102. data/lib/action_dispatch/routing/inspector.rb +6 -30
  103. data/lib/action_dispatch/routing/mapper.rb +447 -322
  104. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  105. data/lib/action_dispatch/routing/redirection.rb +3 -3
  106. data/lib/action_dispatch/routing/route_set.rb +124 -227
  107. data/lib/action_dispatch/routing/url_for.rb +27 -10
  108. data/lib/action_dispatch/testing/assertions.rb +1 -1
  109. data/lib/action_dispatch/testing/assertions/response.rb +27 -9
  110. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  111. data/lib/action_dispatch/testing/integration.rb +237 -76
  112. data/lib/action_dispatch/testing/test_process.rb +5 -5
  113. data/lib/action_dispatch/testing/test_request.rb +12 -21
  114. data/lib/action_dispatch/testing/test_response.rb +1 -4
  115. data/lib/action_pack.rb +1 -1
  116. data/lib/action_pack/gem_version.rb +4 -4
  117. metadata +26 -25
  118. data/lib/action_controller/metal/hide_actions.rb +0 -40
  119. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  120. data/lib/action_controller/middleware.rb +0 -39
  121. data/lib/action_controller/model_naming.rb +0 -12
  122. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  123. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  124. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  125. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -13,12 +13,14 @@ require 'action_dispatch/http/url'
13
13
  require 'active_support/core_ext/array/conversions'
14
14
 
15
15
  module ActionDispatch
16
- class Request < Rack::Request
16
+ class Request
17
+ include Rack::Request::Helpers
17
18
  include ActionDispatch::Http::Cache::Request
18
19
  include ActionDispatch::Http::MimeNegotiation
19
20
  include ActionDispatch::Http::Parameters
20
21
  include ActionDispatch::Http::FilterParameters
21
22
  include ActionDispatch::Http::URL
23
+ include Rack::Request::Env
22
24
 
23
25
  autoload :Session, 'action_dispatch/request/session'
24
26
  autoload :Utils, 'action_dispatch/request/utils'
@@ -29,19 +31,28 @@ module ActionDispatch
29
31
  PATH_TRANSLATED REMOTE_HOST
30
32
  REMOTE_IDENT REMOTE_USER REMOTE_ADDR
31
33
  SERVER_NAME SERVER_PROTOCOL
34
+ ORIGINAL_SCRIPT_NAME
32
35
 
33
36
  HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
34
37
  HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
35
- HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
38
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
39
+ HTTP_X_FORWARDED_FOR HTTP_ORIGIN HTTP_VERSION
40
+ HTTP_X_CSRF_TOKEN HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST
41
+ SERVER_ADDR
42
+ ].freeze
36
43
 
37
44
  ENV_METHODS.each do |env|
38
45
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
39
46
  def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
40
- @env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"]
47
+ get_header "#{env}".freeze # get_header "HTTP_ACCEPT_CHARSET".freeze
41
48
  end # end
42
49
  METHOD
43
50
  end
44
51
 
52
+ def self.empty
53
+ new({})
54
+ end
55
+
45
56
  def initialize(env)
46
57
  super
47
58
  @method = nil
@@ -50,7 +61,9 @@ module ActionDispatch
50
61
  @original_fullpath = nil
51
62
  @fullpath = nil
52
63
  @ip = nil
53
- @uuid = nil
64
+ end
65
+
66
+ def commit_cookie_jar! # :nodoc:
54
67
  end
55
68
 
56
69
  def check_path_parameters!
@@ -59,13 +72,32 @@ module ActionDispatch
59
72
  path_parameters.each do |key, value|
60
73
  next unless value.respond_to?(:valid_encoding?)
61
74
  unless value.valid_encoding?
62
- raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
75
+ raise ActionController::BadRequest, "Invalid parameter encoding: #{key} => #{value.inspect}"
63
76
  end
64
77
  end
65
78
  end
66
79
 
80
+ PASS_NOT_FOUND = Class.new { # :nodoc:
81
+ def self.action(_); self; end
82
+ def self.call(_); [404, {'X-Cascade' => 'pass'}, []]; end
83
+ }
84
+
85
+ def controller_class
86
+ check_path_parameters!
87
+ params = path_parameters
88
+
89
+ if params.key?(:controller)
90
+ controller_param = params[:controller].underscore
91
+ params[:action] ||= 'index'
92
+ const_name = "#{controller_param.camelize}Controller"
93
+ ActiveSupport::Dependencies.constantize(const_name)
94
+ else
95
+ PASS_NOT_FOUND
96
+ end
97
+ end
98
+
67
99
  def key?(key)
68
- @env.key?(key)
100
+ has_header? key
69
101
  end
70
102
 
71
103
  # List of HTTP request methods from the following RFCs:
@@ -102,15 +134,50 @@ module ActionDispatch
102
134
  # the application should use), this \method returns the overridden
103
135
  # value, not the original.
104
136
  def request_method
105
- @request_method ||= check_method(env["REQUEST_METHOD"])
137
+ @request_method ||= check_method(super)
138
+ end
139
+
140
+ def routes # :nodoc:
141
+ get_header("action_dispatch.routes".freeze)
142
+ end
143
+
144
+ def routes=(routes) # :nodoc:
145
+ set_header("action_dispatch.routes".freeze, routes)
146
+ end
147
+
148
+ def engine_script_name(_routes) # :nodoc:
149
+ get_header(_routes.env_key)
150
+ end
151
+
152
+ def engine_script_name=(name) # :nodoc:
153
+ set_header(routes.env_key, name.dup)
106
154
  end
107
155
 
108
156
  def request_method=(request_method) #:nodoc:
109
157
  if check_method(request_method)
110
- @request_method = env["REQUEST_METHOD"] = request_method
158
+ @request_method = set_header("REQUEST_METHOD", request_method)
111
159
  end
112
160
  end
113
161
 
162
+ def controller_instance # :nodoc:
163
+ get_header('action_controller.instance'.freeze)
164
+ end
165
+
166
+ def controller_instance=(controller) # :nodoc:
167
+ set_header('action_controller.instance'.freeze, controller)
168
+ end
169
+
170
+ def http_auth_salt
171
+ get_header "action_dispatch.http_auth_salt"
172
+ end
173
+
174
+ def show_exceptions? # :nodoc:
175
+ # We're treating `nil` as "unset", and we want the default setting to be
176
+ # `true`. This logic should be extracted to `env_config` and calculated
177
+ # once.
178
+ !(get_header('action_dispatch.show_exceptions'.freeze) == false)
179
+ end
180
+
114
181
  # Returns a symbol form of the #request_method
115
182
  def request_method_symbol
116
183
  HTTP_METHOD_LOOKUP[request_method]
@@ -120,7 +187,7 @@ module ActionDispatch
120
187
  # even if it was overridden by middleware. See #request_method for
121
188
  # more information.
122
189
  def method
123
- @method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD'])
190
+ @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD'))
124
191
  end
125
192
 
126
193
  # Returns a symbol form of the #method
@@ -128,47 +195,11 @@ module ActionDispatch
128
195
  HTTP_METHOD_LOOKUP[method]
129
196
  end
130
197
 
131
- # Is this a GET (or HEAD) request?
132
- # Equivalent to <tt>request.request_method_symbol == :get</tt>.
133
- def get?
134
- HTTP_METHOD_LOOKUP[request_method] == :get
135
- end
136
-
137
- # Is this a POST request?
138
- # Equivalent to <tt>request.request_method_symbol == :post</tt>.
139
- def post?
140
- HTTP_METHOD_LOOKUP[request_method] == :post
141
- end
142
-
143
- # Is this a PATCH request?
144
- # Equivalent to <tt>request.request_method == :patch</tt>.
145
- def patch?
146
- HTTP_METHOD_LOOKUP[request_method] == :patch
147
- end
148
-
149
- # Is this a PUT request?
150
- # Equivalent to <tt>request.request_method_symbol == :put</tt>.
151
- def put?
152
- HTTP_METHOD_LOOKUP[request_method] == :put
153
- end
154
-
155
- # Is this a DELETE request?
156
- # Equivalent to <tt>request.request_method_symbol == :delete</tt>.
157
- def delete?
158
- HTTP_METHOD_LOOKUP[request_method] == :delete
159
- end
160
-
161
- # Is this a HEAD request?
162
- # Equivalent to <tt>request.request_method_symbol == :head</tt>.
163
- def head?
164
- HTTP_METHOD_LOOKUP[request_method] == :head
165
- end
166
-
167
198
  # Provides access to the request's HTTP headers, for example:
168
199
  #
169
200
  # request.headers["Content-Type"] # => "text/plain"
170
201
  def headers
171
- Http::Headers.new(@env)
202
+ @headers ||= Http::Headers.new(self)
172
203
  end
173
204
 
174
205
  # Returns a +String+ with the last requested path including their params.
@@ -179,7 +210,7 @@ module ActionDispatch
179
210
  # # get '/foo?bar'
180
211
  # request.original_fullpath # => '/foo?bar'
181
212
  def original_fullpath
182
- @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
213
+ @original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath)
183
214
  end
184
215
 
185
216
  # Returns the +String+ full path including params of the last URL requested.
@@ -218,62 +249,84 @@ module ActionDispatch
218
249
  # (case-insensitive), which may need to be manually added depending on the
219
250
  # choice of JavaScript libraries and frameworks.
220
251
  def xml_http_request?
221
- @env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
252
+ get_header('HTTP_X_REQUESTED_WITH') =~ /XMLHttpRequest/i
222
253
  end
223
254
  alias :xhr? :xml_http_request?
224
255
 
256
+ # Returns the IP address of client as a +String+.
225
257
  def ip
226
258
  @ip ||= super
227
259
  end
228
260
 
229
- # Originating IP address, usually set by the RemoteIp middleware.
261
+ # Returns the IP address of client as a +String+,
262
+ # usually set by the RemoteIp middleware.
230
263
  def remote_ip
231
- @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
264
+ @remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
232
265
  end
233
266
 
267
+ def remote_ip=(remote_ip)
268
+ set_header "action_dispatch.remote_ip".freeze, remote_ip
269
+ end
270
+
271
+ ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc:
272
+
234
273
  # Returns the unique request id, which is based on either the X-Request-Id header that can
235
274
  # be generated by a firewall, load balancer, or web server or by the RequestId middleware
236
275
  # (which sets the action_dispatch.request_id environment variable).
237
276
  #
238
277
  # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
239
278
  # This relies on the rack variable set by the ActionDispatch::RequestId middleware.
240
- def uuid
241
- @uuid ||= env["action_dispatch.request_id"]
279
+ def request_id
280
+ get_header ACTION_DISPATCH_REQUEST_ID
242
281
  end
243
282
 
283
+ def request_id=(id) # :nodoc:
284
+ set_header ACTION_DISPATCH_REQUEST_ID, id
285
+ end
286
+
287
+ alias_method :uuid, :request_id
288
+
244
289
  # Returns the lowercase name of the HTTP server software.
245
290
  def server_software
246
- (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
291
+ (get_header('SERVER_SOFTWARE') && /^([a-zA-Z]+)/ =~ get_header('SERVER_SOFTWARE')) ? $1.downcase : nil
247
292
  end
248
293
 
249
294
  # Read the request \body. This is useful for web services that need to
250
295
  # work with raw requests directly.
251
296
  def raw_post
252
- unless @env.include? 'RAW_POST_DATA'
297
+ unless has_header? 'RAW_POST_DATA'
253
298
  raw_post_body = body
254
- @env['RAW_POST_DATA'] = raw_post_body.read(content_length)
299
+ set_header('RAW_POST_DATA', raw_post_body.read(content_length))
255
300
  raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
256
301
  end
257
- @env['RAW_POST_DATA']
302
+ get_header 'RAW_POST_DATA'
258
303
  end
259
304
 
260
305
  # The request body is an IO input stream. If the RAW_POST_DATA environment
261
306
  # variable is already set, wrap it in a StringIO.
262
307
  def body
263
- if raw_post = @env['RAW_POST_DATA']
308
+ if raw_post = get_header('RAW_POST_DATA')
264
309
  raw_post.force_encoding(Encoding::BINARY)
265
310
  StringIO.new(raw_post)
266
311
  else
267
- @env['rack.input']
312
+ body_stream
268
313
  end
269
314
  end
270
315
 
316
+ # Determine whether the request body contains form-data by checking
317
+ # the request Content-Type for one of the media-types:
318
+ # "application/x-www-form-urlencoded" or "multipart/form-data". The
319
+ # list of form-data media types can be modified through the
320
+ # +FORM_DATA_MEDIA_TYPES+ array.
321
+ #
322
+ # A request body is not assumed to contain form-data when no
323
+ # Content-Type header is provided and the request_method is POST.
271
324
  def form_data?
272
- FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s)
325
+ FORM_DATA_MEDIA_TYPES.include?(media_type)
273
326
  end
274
327
 
275
328
  def body_stream #:nodoc:
276
- @env['rack.input']
329
+ get_header('rack.input')
277
330
  end
278
331
 
279
332
  # TODO This should be broken apart into AD::Request::Session and probably
@@ -284,60 +337,71 @@ module ActionDispatch
284
337
  else
285
338
  self.session = {}
286
339
  end
287
- @env['action_dispatch.request.flash_hash'] = nil
340
+ self.flash = nil
288
341
  end
289
342
 
290
343
  def session=(session) #:nodoc:
291
- Session.set @env, session
344
+ Session.set self, session
292
345
  end
293
346
 
294
347
  def session_options=(options)
295
- Session::Options.set @env, options
348
+ Session::Options.set self, options
296
349
  end
297
350
 
298
351
  # Override Rack's GET method to support indifferent access
299
352
  def GET
300
- @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
353
+ fetch_header("action_dispatch.request.query_parameters") do |k|
354
+ rack_query_params = super || {}
355
+ # Check for non UTF-8 parameter values, which would cause errors later
356
+ Request::Utils.check_param_encoding(rack_query_params)
357
+ set_header k, Request::Utils.normalize_encode_params(rack_query_params)
358
+ end
301
359
  rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
302
- raise ActionController::BadRequest.new(:query, e)
360
+ raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
303
361
  end
304
362
  alias :query_parameters :GET
305
363
 
306
364
  # Override Rack's POST method to support indifferent access
307
365
  def POST
308
- @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
366
+ fetch_header("action_dispatch.request.request_parameters") do
367
+ pr = parse_formatted_parameters(params_parsers) do |params|
368
+ super || {}
369
+ end
370
+ self.request_parameters = Request::Utils.normalize_encode_params(pr)
371
+ end
372
+ rescue ParamsParser::ParseError # one of the parse strategies blew up
373
+ self.request_parameters = Request::Utils.normalize_encode_params(super || {})
374
+ raise
309
375
  rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
310
- raise ActionController::BadRequest.new(:request, e)
376
+ raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
311
377
  end
312
378
  alias :request_parameters :POST
313
379
 
314
380
  # Returns the authorization header regardless of whether it was specified directly or through one of the
315
381
  # proxy alternatives.
316
382
  def authorization
317
- @env['HTTP_AUTHORIZATION'] ||
318
- @env['X-HTTP_AUTHORIZATION'] ||
319
- @env['X_HTTP_AUTHORIZATION'] ||
320
- @env['REDIRECT_X_HTTP_AUTHORIZATION']
383
+ get_header('HTTP_AUTHORIZATION') ||
384
+ get_header('X-HTTP_AUTHORIZATION') ||
385
+ get_header('X_HTTP_AUTHORIZATION') ||
386
+ get_header('REDIRECT_X_HTTP_AUTHORIZATION')
321
387
  end
322
388
 
323
- # True if the request came from localhost, 127.0.0.1.
389
+ # True if the request came from localhost, 127.0.0.1, or ::1.
324
390
  def local?
325
391
  LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
326
392
  end
327
393
 
328
- # Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility.
329
- def deep_munge(hash)
330
- ActiveSupport::Deprecation.warn(
331
- 'This method has been extracted into `ActionDispatch::Request::Utils.deep_munge`. Please start using that instead.'
332
- )
394
+ def request_parameters=(params)
395
+ raise if params.nil?
396
+ set_header("action_dispatch.request.request_parameters".freeze, params)
397
+ end
333
398
 
334
- Utils.deep_munge(hash)
399
+ def logger
400
+ get_header("action_dispatch.logger".freeze)
335
401
  end
336
402
 
337
- protected
338
- def parse_query(qs)
339
- Utils.deep_munge(super)
340
- end
403
+ def commit_flash
404
+ end
341
405
 
342
406
  private
343
407
  def check_method(name)
@@ -1,6 +1,4 @@
1
1
  require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_support/core_ext/string/filters'
3
- require 'active_support/deprecation'
4
2
  require 'action_dispatch/http/filter_redirect'
5
3
  require 'monitor'
6
4
 
@@ -34,46 +32,56 @@ module ActionDispatch # :nodoc:
34
32
  # end
35
33
  # end
36
34
  class Response
35
+ class Header < DelegateClass(Hash) # :nodoc:
36
+ def initialize(response, header)
37
+ @response = response
38
+ super(header)
39
+ end
40
+
41
+ def []=(k,v)
42
+ if @response.sending? || @response.sent?
43
+ raise ActionDispatch::IllegalStateError, 'header already sent'
44
+ end
45
+
46
+ super
47
+ end
48
+
49
+ def merge(other)
50
+ self.class.new @response, __getobj__.merge(other)
51
+ end
52
+
53
+ def to_hash
54
+ __getobj__.dup
55
+ end
56
+ end
57
+
37
58
  # The request that the response is responding to.
38
59
  attr_accessor :request
39
60
 
40
61
  # The HTTP status code.
41
62
  attr_reader :status
42
63
 
43
- attr_writer :sending_file
64
+ # Get headers for this response.
65
+ attr_reader :header
44
66
 
45
- # Get and set headers for this response.
46
- attr_accessor :header
47
-
48
- alias_method :headers=, :header=
49
67
  alias_method :headers, :header
50
68
 
51
69
  delegate :[], :[]=, :to => :@header
52
70
  delegate :each, :to => :@stream
53
71
 
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
67
-
68
72
  CONTENT_TYPE = "Content-Type".freeze
69
73
  SET_COOKIE = "Set-Cookie".freeze
70
74
  LOCATION = "Location".freeze
71
- NO_CONTENT_CODES = [204, 304]
75
+ NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
72
76
 
73
77
  cattr_accessor(:default_charset) { "utf-8" }
74
78
  cattr_accessor(:default_headers)
75
79
 
76
80
  include Rack::Response::Helpers
81
+ # Aliasing these off because AD::Http::Cache::Response defines them
82
+ alias :_cache_control :cache_control
83
+ alias :_cache_control= :cache_control=
84
+
77
85
  include ActionDispatch::Http::FilterRedirect
78
86
  include ActionDispatch::Http::Cache::Response
79
87
  include MonitorMixin
@@ -83,11 +91,21 @@ module ActionDispatch # :nodoc:
83
91
  @response = response
84
92
  @buf = buf
85
93
  @closed = false
94
+ @str_body = nil
95
+ end
96
+
97
+ def body
98
+ @str_body ||= begin
99
+ buf = ''
100
+ each { |chunk| buf << chunk }
101
+ buf
102
+ end
86
103
  end
87
104
 
88
105
  def write(string)
89
106
  raise IOError, "closed stream" if closed?
90
107
 
108
+ @str_body = nil
91
109
  @response.commit!
92
110
  @buf.push string
93
111
  end
@@ -112,37 +130,40 @@ module ActionDispatch # :nodoc:
112
130
  end
113
131
  end
114
132
 
133
+ def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
134
+ header = merge_default_headers(header, default_headers)
135
+ new status, header, body
136
+ end
137
+
138
+ def self.merge_default_headers(original, default)
139
+ default.respond_to?(:merge) ? default.merge(original) : original
140
+ end
141
+
115
142
  # The underlying body, as a streamable object.
116
143
  attr_reader :stream
117
144
 
118
- def initialize(status = 200, header = {}, body = [], options = {})
145
+ def initialize(status = 200, header = {}, body = [])
119
146
  super()
120
147
 
121
- default_headers = options.fetch(:default_headers, self.class.default_headers)
122
- header = merge_default_headers(header, default_headers)
148
+ @header = Header.new(self, header)
123
149
 
124
- self.body, self.header, self.status = body, header, status
150
+ self.body, self.status = body, status
125
151
 
126
- @sending_file = false
127
- @blank = false
128
152
  @cv = new_cond
129
153
  @committed = false
130
154
  @sending = false
131
155
  @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
156
 
141
157
  prepare_cache_control!
142
158
 
143
159
  yield self if block_given?
144
160
  end
145
161
 
162
+ def has_header?(key); headers.key? key; end
163
+ def get_header(key); headers[key]; end
164
+ def set_header(key, v); headers[key] = v; end
165
+ def delete_header(key); headers.delete key; end
166
+
146
167
  def await_commit
147
168
  synchronize do
148
169
  @cv.wait_until { @committed }
@@ -187,7 +208,49 @@ module ActionDispatch # :nodoc:
187
208
 
188
209
  # Sets the HTTP content type.
189
210
  def content_type=(content_type)
190
- @content_type = content_type.to_s
211
+ header_info = parse_content_type
212
+ set_content_type content_type.to_s, header_info.charset || self.class.default_charset
213
+ end
214
+
215
+ # Sets the HTTP response's content MIME type. For example, in the controller
216
+ # you could write this:
217
+ #
218
+ # response.content_type = "text/plain"
219
+ #
220
+ # If a character set has been defined for this response (see charset=) then
221
+ # the character set information will also be included in the content type
222
+ # information.
223
+
224
+ def content_type
225
+ parse_content_type.mime_type
226
+ end
227
+
228
+ def sending_file=(v)
229
+ if true == v
230
+ self.charset = false
231
+ end
232
+ end
233
+
234
+ # Sets the HTTP character set. In case of nil parameter
235
+ # it sets the charset to utf-8.
236
+ #
237
+ # response.charset = 'utf-16' # => 'utf-16'
238
+ # response.charset = nil # => 'utf-8'
239
+ def charset=(charset)
240
+ header_info = parse_content_type
241
+ if false == charset
242
+ set_header CONTENT_TYPE, header_info.mime_type
243
+ else
244
+ content_type = header_info.mime_type
245
+ set_content_type content_type, charset || self.class.default_charset
246
+ end
247
+ end
248
+
249
+ # The charset of the response. HTML wants to know the encoding of the
250
+ # content you're giving them, so we need to send that along.
251
+ def charset
252
+ header_info = parse_content_type
253
+ header_info.charset || self.class.default_charset
191
254
  end
192
255
 
193
256
  # The response code of the request.
@@ -216,17 +279,15 @@ module ActionDispatch # :nodoc:
216
279
  # Returns the content of the response as a string. This contains the contents
217
280
  # of any calls to <tt>render</tt>.
218
281
  def body
219
- strings = []
220
- each { |part| strings << part.to_s }
221
- strings.join
282
+ @stream.body
222
283
  end
223
284
 
224
- EMPTY = " "
285
+ def write(string)
286
+ @stream.write string
287
+ end
225
288
 
226
289
  # Allows you to manually set or override the response body.
227
290
  def body=(body)
228
- @blank = true if body == EMPTY
229
-
230
291
  if body.respond_to?(:to_path)
231
292
  @stream = body
232
293
  else
@@ -236,31 +297,49 @@ module ActionDispatch # :nodoc:
236
297
  end
237
298
  end
238
299
 
239
- def body_parts
240
- parts = []
241
- @stream.each { |x| parts << x }
242
- parts
243
- end
300
+ # Avoid having to pass an open file handle as the response body.
301
+ # Rack::Sendfile will usually intercept the response and uses
302
+ # the path directly, so there is no reason to open the file.
303
+ class FileBody #:nodoc:
304
+ attr_reader :to_path
305
+
306
+ def initialize(path)
307
+ @to_path = path
308
+ end
244
309
 
245
- def set_cookie(key, value)
246
- ::Rack::Utils.set_cookie_header!(header, key, value)
310
+ def body
311
+ File.binread(to_path)
312
+ end
313
+
314
+ # Stream the file's contents if Rack::Sendfile isn't present.
315
+ def each
316
+ File.open(to_path, 'rb') do |file|
317
+ while chunk = file.read(16384)
318
+ yield chunk
319
+ end
320
+ end
321
+ end
247
322
  end
248
323
 
249
- def delete_cookie(key, value={})
250
- ::Rack::Utils.delete_cookie_header!(header, key, value)
324
+ # Send the file stored at +path+ as the response body.
325
+ def send_file(path)
326
+ commit!
327
+ @stream = FileBody.new(path)
251
328
  end
252
329
 
253
- # The location header we'll be responding with.
254
- def location
255
- headers[LOCATION]
330
+ def reset_body!
331
+ @stream = build_buffer(self, [])
256
332
  end
257
- alias_method :redirect_url, :location
258
333
 
259
- # Sets the location header we'll be responding with.
260
- def location=(url)
261
- headers[LOCATION] = url
334
+ def body_parts
335
+ parts = []
336
+ @stream.each { |x| parts << x }
337
+ parts
262
338
  end
263
339
 
340
+ # The location header we'll be responding with.
341
+ alias_method :redirect_url, :location
342
+
264
343
  def close
265
344
  stream.close if stream.respond_to?(:close)
266
345
  end
@@ -277,34 +356,21 @@ module ActionDispatch # :nodoc:
277
356
  end
278
357
 
279
358
  # Turns the Response into a Rack-compatible array of the status, headers,
280
- # and body. Allows explict splatting:
359
+ # and body. Allows explicit splatting:
281
360
  #
282
361
  # status, headers, body = *response
283
362
  def to_a
363
+ commit!
284
364
  rack_response @status, @header.to_hash
285
365
  end
286
366
  alias prepare! to_a
287
367
 
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
368
  # Returns the response cookies, converted to a Hash of (name => value) pairs
303
369
  #
304
370
  # assert_equal 'AuthorOfNewPage', r.cookies['author']
305
371
  def cookies
306
372
  cookies = {}
307
- if header = self[SET_COOKIE]
373
+ if header = get_header(SET_COOKIE)
308
374
  header = header.split("\n") if header.respond_to?(:to_str)
309
375
  header.each do |cookie|
310
376
  if pair = cookie.split(';').first
@@ -318,14 +384,36 @@ module ActionDispatch # :nodoc:
318
384
 
319
385
  private
320
386
 
321
- def before_committed
387
+ ContentTypeHeader = Struct.new :mime_type, :charset
388
+ NullContentTypeHeader = ContentTypeHeader.new nil, nil
389
+
390
+ def parse_content_type
391
+ content_type = get_header CONTENT_TYPE
392
+ if content_type
393
+ type, charset = content_type.split(/;\s*charset=/)
394
+ type = nil if type.empty?
395
+ ContentTypeHeader.new(type, charset)
396
+ else
397
+ NullContentTypeHeader
398
+ end
322
399
  end
323
400
 
324
- def before_sending
401
+ def set_content_type(content_type, charset)
402
+ type = (content_type || '').dup
403
+ type << "; charset=#{charset}" if charset
404
+ set_header CONTENT_TYPE, type
325
405
  end
326
406
 
327
- def merge_default_headers(original, default)
328
- default.respond_to?(:merge) ? default.merge(original) : original
407
+ def before_committed
408
+ return if committed?
409
+ assign_default_content_type_and_charset!
410
+ handle_conditional_get!
411
+ handle_no_content!
412
+ end
413
+
414
+ def before_sending
415
+ headers.freeze
416
+ request.commit_cookie_jar! unless committed?
329
417
  end
330
418
 
331
419
  def build_buffer(response, body)
@@ -336,20 +424,12 @@ module ActionDispatch # :nodoc:
336
424
  body.respond_to?(:each) ? body : [body]
337
425
  end
338
426
 
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?
427
+ def assign_default_content_type_and_charset!
428
+ return if content_type
347
429
 
348
- headers[CONTENT_TYPE] = type
349
- end
350
-
351
- def append_charset?
352
- !@sending_file && @charset != false
430
+ ct = parse_content_type
431
+ set_content_type(ct.mime_type || Mime[:html].to_s,
432
+ ct.charset || self.class.default_charset)
353
433
  end
354
434
 
355
435
  class RackBody
@@ -388,14 +468,15 @@ module ActionDispatch # :nodoc:
388
468
  end
389
469
  end
390
470
 
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
-
471
+ def handle_no_content!
397
472
  if NO_CONTENT_CODES.include?(@status)
398
- header.delete CONTENT_TYPE
473
+ @header.delete CONTENT_TYPE
474
+ @header.delete 'Content-Length'
475
+ end
476
+ end
477
+
478
+ def rack_response(status, header)
479
+ if NO_CONTENT_CODES.include?(status)
399
480
  [status, header, []]
400
481
  else
401
482
  [status, header, RackBody.new(self)]