actionpack 4.0.1 → 4.2.11.1

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 (241) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +402 -1173
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -7
  5. data/lib/abstract_controller/base.rb +39 -7
  6. data/lib/abstract_controller/callbacks.rb +32 -53
  7. data/lib/abstract_controller/collector.rb +11 -1
  8. data/lib/abstract_controller/helpers.rb +26 -16
  9. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  10. data/lib/abstract_controller/rendering.rb +57 -127
  11. data/lib/abstract_controller/url_for.rb +1 -1
  12. data/lib/abstract_controller.rb +1 -2
  13. data/lib/action_controller/base.rb +19 -10
  14. data/lib/action_controller/caching/fragments.rb +7 -1
  15. data/lib/action_controller/caching.rb +2 -12
  16. data/lib/action_controller/log_subscriber.rb +29 -20
  17. data/lib/action_controller/metal/conditional_get.rb +37 -12
  18. data/lib/action_controller/metal/data_streaming.rb +1 -1
  19. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  20. data/lib/action_controller/metal/exceptions.rb +1 -1
  21. data/lib/action_controller/metal/flash.rb +17 -0
  22. data/lib/action_controller/metal/force_ssl.rb +2 -2
  23. data/lib/action_controller/metal/head.rb +8 -6
  24. data/lib/action_controller/metal/helpers.rb +6 -2
  25. data/lib/action_controller/metal/http_authentication.rb +45 -23
  26. data/lib/action_controller/metal/instrumentation.rb +9 -6
  27. data/lib/action_controller/metal/live.rb +173 -20
  28. data/lib/action_controller/metal/mime_responds.rb +127 -232
  29. data/lib/action_controller/metal/params_wrapper.rb +16 -9
  30. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  31. data/lib/action_controller/metal/redirecting.rb +34 -26
  32. data/lib/action_controller/metal/renderers.rb +39 -12
  33. data/lib/action_controller/metal/rendering.rb +41 -14
  34. data/lib/action_controller/metal/request_forgery_protection.rb +147 -19
  35. data/lib/action_controller/metal/streaming.rb +19 -21
  36. data/lib/action_controller/metal/strong_parameters.rb +166 -22
  37. data/lib/action_controller/metal/testing.rb +0 -1
  38. data/lib/action_controller/metal/url_for.rb +11 -12
  39. data/lib/action_controller/metal.rb +14 -8
  40. data/lib/action_controller/model_naming.rb +1 -1
  41. data/lib/action_controller/railtie.rb +5 -1
  42. data/lib/action_controller/test_case.rb +160 -94
  43. data/lib/action_controller.rb +2 -18
  44. data/lib/action_dispatch/http/cache.rb +5 -4
  45. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  46. data/lib/action_dispatch/http/filter_redirect.rb +5 -4
  47. data/lib/action_dispatch/http/headers.rb +46 -10
  48. data/lib/action_dispatch/http/mime_negotiation.rb +31 -4
  49. data/lib/action_dispatch/http/mime_type.rb +25 -26
  50. data/lib/action_dispatch/http/mime_types.rb +1 -0
  51. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  52. data/lib/action_dispatch/http/parameters.rb +25 -41
  53. data/lib/action_dispatch/http/request.rb +49 -32
  54. data/lib/action_dispatch/http/response.rb +127 -25
  55. data/lib/action_dispatch/http/upload.rb +9 -21
  56. data/lib/action_dispatch/http/url.rb +97 -70
  57. data/lib/action_dispatch/journey/formatter.rb +35 -19
  58. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  59. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  60. data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -33
  61. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  62. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  63. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  64. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  65. data/lib/action_dispatch/journey/parser.rb +51 -59
  66. data/lib/action_dispatch/journey/parser.y +12 -10
  67. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  68. data/lib/action_dispatch/journey/route.rb +8 -19
  69. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  70. data/lib/action_dispatch/journey/router/utils.rb +54 -18
  71. data/lib/action_dispatch/journey/router.rb +53 -75
  72. data/lib/action_dispatch/journey/routes.rb +4 -0
  73. data/lib/action_dispatch/journey/scanner.rb +5 -5
  74. data/lib/action_dispatch/journey/visitors.rb +81 -60
  75. data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
  76. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  77. data/lib/action_dispatch/middleware/callbacks.rb +7 -7
  78. data/lib/action_dispatch/middleware/cookies.rb +119 -43
  79. data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -13
  80. data/lib/action_dispatch/middleware/exception_wrapper.rb +60 -20
  81. data/lib/action_dispatch/middleware/flash.rb +37 -24
  82. data/lib/action_dispatch/middleware/params_parser.rb +2 -2
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +11 -2
  85. data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
  86. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +8 -7
  89. data/lib/action_dispatch/middleware/show_exceptions.rb +6 -2
  90. data/lib/action_dispatch/middleware/ssl.rb +10 -7
  91. data/lib/action_dispatch/middleware/static.rb +79 -23
  92. data/lib/action_dispatch/middleware/templates/rescues/{_request_and_response.erb → _request_and_response.html.erb} +0 -0
  93. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  94. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
  95. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  96. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  97. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +1 -1
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  102. data/lib/action_dispatch/middleware/templates/rescues/{routing_error.erb → routing_error.html.erb} +3 -1
  103. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  106. data/lib/action_dispatch/middleware/templates/rescues/{unknown_action.erb → unknown_action.html.erb} +1 -1
  107. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  108. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
  109. data/lib/action_dispatch/railtie.rb +5 -2
  110. data/lib/action_dispatch/request/session.rb +12 -0
  111. data/lib/action_dispatch/request/utils.rb +35 -0
  112. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  113. data/lib/action_dispatch/routing/inspector.rb +11 -17
  114. data/lib/action_dispatch/routing/mapper.rb +519 -312
  115. data/lib/action_dispatch/routing/polymorphic_routes.rb +204 -79
  116. data/lib/action_dispatch/routing/redirection.rb +51 -26
  117. data/lib/action_dispatch/routing/route_set.rb +331 -206
  118. data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
  119. data/lib/action_dispatch/routing/url_for.rb +19 -5
  120. data/lib/action_dispatch/routing.rb +9 -6
  121. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  122. data/lib/action_dispatch/testing/assertions/response.rb +9 -15
  123. data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
  124. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  125. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  126. data/lib/action_dispatch/testing/assertions.rb +11 -7
  127. data/lib/action_dispatch/testing/integration.rb +31 -29
  128. data/lib/action_dispatch/testing/test_request.rb +1 -1
  129. data/lib/action_dispatch/testing/test_response.rb +1 -5
  130. data/lib/action_dispatch.rb +5 -8
  131. data/lib/action_pack/gem_version.rb +15 -0
  132. data/lib/action_pack/version.rb +4 -7
  133. data/lib/action_pack.rb +1 -1
  134. metadata +77 -159
  135. data/lib/abstract_controller/layouts.rb +0 -423
  136. data/lib/abstract_controller/view_paths.rb +0 -96
  137. data/lib/action_controller/deprecated/integration_test.rb +0 -5
  138. data/lib/action_controller/deprecated.rb +0 -7
  139. data/lib/action_controller/metal/responder.rb +0 -287
  140. data/lib/action_controller/record_identifier.rb +0 -31
  141. data/lib/action_controller/vendor/html-scanner.rb +0 -5
  142. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -24
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -7
  144. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -43
  145. data/lib/action_view/base.rb +0 -201
  146. data/lib/action_view/buffers.rb +0 -49
  147. data/lib/action_view/context.rb +0 -36
  148. data/lib/action_view/dependency_tracker.rb +0 -93
  149. data/lib/action_view/digestor.rb +0 -113
  150. data/lib/action_view/flows.rb +0 -76
  151. data/lib/action_view/helpers/active_model_helper.rb +0 -49
  152. data/lib/action_view/helpers/asset_tag_helper.rb +0 -320
  153. data/lib/action_view/helpers/asset_url_helper.rb +0 -355
  154. data/lib/action_view/helpers/atom_feed_helper.rb +0 -203
  155. data/lib/action_view/helpers/cache_helper.rb +0 -196
  156. data/lib/action_view/helpers/capture_helper.rb +0 -216
  157. data/lib/action_view/helpers/controller_helper.rb +0 -25
  158. data/lib/action_view/helpers/csrf_helper.rb +0 -30
  159. data/lib/action_view/helpers/date_helper.rb +0 -1083
  160. data/lib/action_view/helpers/debug_helper.rb +0 -39
  161. data/lib/action_view/helpers/form_helper.rb +0 -1880
  162. data/lib/action_view/helpers/form_options_helper.rb +0 -838
  163. data/lib/action_view/helpers/form_tag_helper.rb +0 -785
  164. data/lib/action_view/helpers/javascript_helper.rb +0 -117
  165. data/lib/action_view/helpers/number_helper.rb +0 -441
  166. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  167. data/lib/action_view/helpers/record_tag_helper.rb +0 -106
  168. data/lib/action_view/helpers/rendering_helper.rb +0 -90
  169. data/lib/action_view/helpers/sanitize_helper.rb +0 -256
  170. data/lib/action_view/helpers/tag_helper.rb +0 -173
  171. data/lib/action_view/helpers/tags/base.rb +0 -148
  172. data/lib/action_view/helpers/tags/check_box.rb +0 -64
  173. data/lib/action_view/helpers/tags/checkable.rb +0 -16
  174. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -44
  175. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -84
  176. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -36
  177. data/lib/action_view/helpers/tags/collection_select.rb +0 -28
  178. data/lib/action_view/helpers/tags/color_field.rb +0 -25
  179. data/lib/action_view/helpers/tags/date_field.rb +0 -13
  180. data/lib/action_view/helpers/tags/date_select.rb +0 -72
  181. data/lib/action_view/helpers/tags/datetime_field.rb +0 -22
  182. data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -19
  183. data/lib/action_view/helpers/tags/datetime_select.rb +0 -8
  184. data/lib/action_view/helpers/tags/email_field.rb +0 -8
  185. data/lib/action_view/helpers/tags/file_field.rb +0 -8
  186. data/lib/action_view/helpers/tags/grouped_collection_select.rb +0 -29
  187. data/lib/action_view/helpers/tags/hidden_field.rb +0 -8
  188. data/lib/action_view/helpers/tags/label.rb +0 -66
  189. data/lib/action_view/helpers/tags/month_field.rb +0 -13
  190. data/lib/action_view/helpers/tags/number_field.rb +0 -18
  191. data/lib/action_view/helpers/tags/password_field.rb +0 -12
  192. data/lib/action_view/helpers/tags/radio_button.rb +0 -31
  193. data/lib/action_view/helpers/tags/range_field.rb +0 -8
  194. data/lib/action_view/helpers/tags/search_field.rb +0 -24
  195. data/lib/action_view/helpers/tags/select.rb +0 -40
  196. data/lib/action_view/helpers/tags/tel_field.rb +0 -8
  197. data/lib/action_view/helpers/tags/text_area.rb +0 -18
  198. data/lib/action_view/helpers/tags/text_field.rb +0 -29
  199. data/lib/action_view/helpers/tags/time_field.rb +0 -13
  200. data/lib/action_view/helpers/tags/time_select.rb +0 -8
  201. data/lib/action_view/helpers/tags/time_zone_select.rb +0 -20
  202. data/lib/action_view/helpers/tags/url_field.rb +0 -8
  203. data/lib/action_view/helpers/tags/week_field.rb +0 -13
  204. data/lib/action_view/helpers/tags.rb +0 -39
  205. data/lib/action_view/helpers/text_helper.rb +0 -443
  206. data/lib/action_view/helpers/translation_helper.rb +0 -107
  207. data/lib/action_view/helpers/url_helper.rb +0 -635
  208. data/lib/action_view/helpers.rb +0 -58
  209. data/lib/action_view/locale/en.yml +0 -56
  210. data/lib/action_view/log_subscriber.rb +0 -30
  211. data/lib/action_view/lookup_context.rb +0 -241
  212. data/lib/action_view/model_naming.rb +0 -12
  213. data/lib/action_view/path_set.rb +0 -77
  214. data/lib/action_view/railtie.rb +0 -43
  215. data/lib/action_view/record_identifier.rb +0 -84
  216. data/lib/action_view/renderer/abstract_renderer.rb +0 -47
  217. data/lib/action_view/renderer/partial_renderer.rb +0 -492
  218. data/lib/action_view/renderer/renderer.rb +0 -50
  219. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -103
  220. data/lib/action_view/renderer/template_renderer.rb +0 -96
  221. data/lib/action_view/routing_url_for.rb +0 -107
  222. data/lib/action_view/tasks/dependencies.rake +0 -17
  223. data/lib/action_view/template/error.rb +0 -138
  224. data/lib/action_view/template/handlers/builder.rb +0 -26
  225. data/lib/action_view/template/handlers/erb.rb +0 -146
  226. data/lib/action_view/template/handlers/raw.rb +0 -11
  227. data/lib/action_view/template/handlers.rb +0 -53
  228. data/lib/action_view/template/resolver.rb +0 -326
  229. data/lib/action_view/template/text.rb +0 -34
  230. data/lib/action_view/template/types.rb +0 -57
  231. data/lib/action_view/template.rb +0 -339
  232. data/lib/action_view/test_case.rb +0 -270
  233. data/lib/action_view/testing/resolvers.rb +0 -50
  234. data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
  235. data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
  236. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
  237. data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
  238. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
  239. data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
  240. data/lib/action_view/vendor/html-scanner.rb +0 -20
  241. data/lib/action_view.rb +0 -93
@@ -1,4 +1,5 @@
1
1
  require 'base64'
2
+ require 'active_support/security_utils'
2
3
 
3
4
  module ActionController
4
5
  # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
@@ -11,11 +12,11 @@ module ActionController
11
12
  # http_basic_authenticate_with name: "dhh", password: "secret", except: :index
12
13
  #
13
14
  # def index
14
- # render text: "Everyone can see me!"
15
+ # render plain: "Everyone can see me!"
15
16
  # end
16
17
  #
17
18
  # def edit
18
- # render text: "I'm only accessible if you know the password"
19
+ # render plain: "I'm only accessible if you know the password"
19
20
  # end
20
21
  # end
21
22
  #
@@ -53,10 +54,8 @@ module ActionController
53
54
  # In your integration tests, you can do something like this:
54
55
  #
55
56
  # def test_access_granted_from_xml
56
- # get(
57
- # "/notes/1.xml", nil,
58
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
59
- # )
57
+ # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
58
+ # get "/notes/1.xml"
60
59
  #
61
60
  # assert_equal 200, status
62
61
  # end
@@ -70,7 +69,11 @@ module ActionController
70
69
  def http_basic_authenticate_with(options = {})
71
70
  before_action(options.except(:name, :password, :realm)) do
72
71
  authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
73
- name == options[:name] && password == options[:password]
72
+ # This comparison uses & so that it doesn't short circuit and
73
+ # uses `variable_size_secure_compare` so that length information
74
+ # isn't leaked.
75
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(name, options[:name]) &
76
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(password, options[:password])
74
77
  end
75
78
  end
76
79
  end
@@ -90,17 +93,29 @@ module ActionController
90
93
  end
91
94
 
92
95
  def authenticate(request, &login_procedure)
93
- unless request.authorization.blank?
96
+ if has_basic_credentials?(request)
94
97
  login_procedure.call(*user_name_and_password(request))
95
98
  end
96
99
  end
97
100
 
101
+ def has_basic_credentials?(request)
102
+ request.authorization.present? && (auth_scheme(request) == 'Basic')
103
+ end
104
+
98
105
  def user_name_and_password(request)
99
- decode_credentials(request).split(/:/, 2)
106
+ decode_credentials(request).split(':', 2)
100
107
  end
101
108
 
102
109
  def decode_credentials(request)
103
- ::Base64.decode64(request.authorization.split(' ', 2).last || '')
110
+ ::Base64.decode64(auth_param(request) || '')
111
+ end
112
+
113
+ def auth_scheme(request)
114
+ request.authorization.split(' ', 2).first
115
+ end
116
+
117
+ def auth_param(request)
118
+ request.authorization.split(' ', 2).second
104
119
  end
105
120
 
106
121
  def encode_credentials(user_name, password)
@@ -109,8 +124,8 @@ module ActionController
109
124
 
110
125
  def authentication_request(controller, realm)
111
126
  controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
112
- controller.response_body = "HTTP Basic: Access denied.\n"
113
127
  controller.status = 401
128
+ controller.response_body = "HTTP Basic: Access denied.\n"
114
129
  end
115
130
  end
116
131
 
@@ -127,11 +142,11 @@ module ActionController
127
142
  # before_action :authenticate, except: [:index]
128
143
  #
129
144
  # def index
130
- # render text: "Everyone can see me!"
145
+ # render plain: "Everyone can see me!"
131
146
  # end
132
147
  #
133
148
  # def edit
134
- # render text: "I'm only accessible if you know the password"
149
+ # render plain: "I'm only accessible if you know the password"
135
150
  # end
136
151
  #
137
152
  # private
@@ -244,8 +259,8 @@ module ActionController
244
259
  def authentication_request(controller, realm, message = nil)
245
260
  message ||= "HTTP Digest: Access denied.\n"
246
261
  authentication_header(controller, realm)
247
- controller.response_body = message
248
262
  controller.status = 401
263
+ controller.response_body = message
249
264
  end
250
265
 
251
266
  def secret_token(request)
@@ -321,11 +336,11 @@ module ActionController
321
336
  # before_action :authenticate, except: [ :index ]
322
337
  #
323
338
  # def index
324
- # render text: "Everyone can see me!"
339
+ # render plain: "Everyone can see me!"
325
340
  # end
326
341
  #
327
342
  # def edit
328
- # render text: "I'm only accessible if you know the password"
343
+ # render plain: "I'm only accessible if you know the password"
329
344
  # end
330
345
  #
331
346
  # private
@@ -385,6 +400,7 @@ module ActionController
385
400
  #
386
401
  # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
387
402
  module Token
403
+ TOKEN_KEY = 'token='
388
404
  TOKEN_REGEX = /^Token /
389
405
  AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
390
406
  extend self
@@ -437,7 +453,7 @@ module ActionController
437
453
  authorization_request = request.authorization.to_s
438
454
  if authorization_request[TOKEN_REGEX]
439
455
  params = token_params_from authorization_request
440
- [params.shift.last, Hash[params].with_indifferent_access]
456
+ [params.shift[1], Hash[params].with_indifferent_access]
441
457
  end
442
458
  end
443
459
 
@@ -450,16 +466,22 @@ module ActionController
450
466
  raw_params.map { |param| param.split %r/=(.+)?/ }
451
467
  end
452
468
 
453
- # This removes the `"` characters wrapping the value.
469
+ # This removes the <tt>"</tt> characters wrapping the value.
454
470
  def rewrite_param_values(array_params)
455
- array_params.each { |param| param.last.gsub! %r/^"|"$/, '' }
471
+ array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
456
472
  end
457
473
 
458
474
  # This method takes an authorization body and splits up the key-value
459
- # pairs by the standardized `:`, `;`, or `\t` delimiters defined in
460
- # `AUTHN_PAIR_DELIMITERS`.
475
+ # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
476
+ # delimiters defined in +AUTHN_PAIR_DELIMITERS+.
461
477
  def raw_params(auth)
462
- auth.sub(TOKEN_REGEX, '').split(/"\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
478
+ _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
479
+
480
+ if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
481
+ _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
482
+ end
483
+
484
+ _raw_params
463
485
  end
464
486
 
465
487
  # Encodes the given token and options into an Authorization header value.
@@ -469,7 +491,7 @@ module ActionController
469
491
  #
470
492
  # Returns String.
471
493
  def encode_credentials(token, options = {})
472
- values = ["token=#{token.to_s.inspect}"] + options.map do |key, value|
494
+ values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
473
495
  "#{key}=#{value.to_s.inspect}"
474
496
  end
475
497
  "Token #{values * ", "}"
@@ -21,17 +21,20 @@ module ActionController
21
21
  :action => self.action_name,
22
22
  :params => request.filtered_parameters,
23
23
  :format => request.format.try(:ref),
24
- :method => request.method,
24
+ :method => request.request_method,
25
25
  :path => (request.fullpath rescue "unknown")
26
26
  }
27
27
 
28
28
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
29
29
 
30
30
  ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
31
- result = super
32
- payload[:status] = response.status
33
- append_info_to_payload(payload)
34
- result
31
+ begin
32
+ result = super
33
+ payload[:status] = response.status
34
+ result
35
+ ensure
36
+ append_info_to_payload(payload)
37
+ end
35
38
  end
36
39
  end
37
40
 
@@ -67,7 +70,7 @@ module ActionController
67
70
 
68
71
  private
69
72
 
70
- # A hook invoked everytime a before callback is halted.
73
+ # A hook invoked every time a before callback is halted.
71
74
  def halted_callback_hook(filter)
72
75
  ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
73
76
  end
@@ -1,5 +1,6 @@
1
1
  require 'action_dispatch/http/response'
2
2
  require 'delegate'
3
+ require 'active_support/json'
3
4
 
4
5
  module ActionController
5
6
  # Mix this module in to your controller, and all actions in that controller
@@ -32,9 +33,99 @@ module ActionController
32
33
  # the main thread. Make sure your actions are thread safe, and this shouldn't
33
34
  # be a problem (don't share state across threads, etc).
34
35
  module Live
36
+ # This class provides the ability to write an SSE (Server Sent Event)
37
+ # to an IO stream. The class is initialized with a stream and can be used
38
+ # to either write a JSON string or an object which can be converted to JSON.
39
+ #
40
+ # Writing an object will convert it into standard SSE format with whatever
41
+ # options you have configured. You may choose to set the following options:
42
+ #
43
+ # 1) Event. If specified, an event with this name will be dispatched on
44
+ # the browser.
45
+ # 2) Retry. The reconnection time in milliseconds used when attempting
46
+ # to send the event.
47
+ # 3) Id. If the connection dies while sending an SSE to the browser, then
48
+ # the server will receive a +Last-Event-ID+ header with value equal to +id+.
49
+ #
50
+ # After setting an option in the constructor of the SSE object, all future
51
+ # SSEs sent across the stream will use those options unless overridden.
52
+ #
53
+ # Example Usage:
54
+ #
55
+ # class MyController < ActionController::Base
56
+ # include ActionController::Live
57
+ #
58
+ # def index
59
+ # response.headers['Content-Type'] = 'text/event-stream'
60
+ # sse = SSE.new(response.stream, retry: 300, event: "event-name")
61
+ # sse.write({ name: 'John'})
62
+ # sse.write({ name: 'John'}, id: 10)
63
+ # sse.write({ name: 'John'}, id: 10, event: "other-event")
64
+ # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
65
+ # ensure
66
+ # sse.close
67
+ # end
68
+ # end
69
+ #
70
+ # Note: SSEs are not currently supported by IE. However, they are supported
71
+ # by Chrome, Firefox, Opera, and Safari.
72
+ class SSE
73
+
74
+ WHITELISTED_OPTIONS = %w( retry event id )
75
+
76
+ def initialize(stream, options = {})
77
+ @stream = stream
78
+ @options = options
79
+ end
80
+
81
+ def close
82
+ @stream.close
83
+ end
84
+
85
+ def write(object, options = {})
86
+ case object
87
+ when String
88
+ perform_write(object, options)
89
+ else
90
+ perform_write(ActiveSupport::JSON.encode(object), options)
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def perform_write(json, options)
97
+ current_options = @options.merge(options).stringify_keys
98
+
99
+ WHITELISTED_OPTIONS.each do |option_name|
100
+ if (option_value = current_options[option_name])
101
+ @stream.write "#{option_name}: #{option_value}\n"
102
+ end
103
+ end
104
+
105
+ message = json.gsub(/\n/, "\ndata: ")
106
+ @stream.write "data: #{message}\n\n"
107
+ end
108
+ end
109
+
110
+ class ClientDisconnected < RuntimeError
111
+ end
112
+
35
113
  class Buffer < ActionDispatch::Response::Buffer #:nodoc:
114
+ include MonitorMixin
115
+
116
+ # Ignore that the client has disconnected.
117
+ #
118
+ # If this value is `true`, calling `write` after the client
119
+ # disconnects will result in the written content being silently
120
+ # discarded. If this value is `false` (the default), a
121
+ # ClientDisconnected exception will be raised.
122
+ attr_accessor :ignore_disconnect
123
+
36
124
  def initialize(response)
37
- @error_callback = nil
125
+ @error_callback = lambda { true }
126
+ @cv = new_cond
127
+ @aborted = false
128
+ @ignore_disconnect = false
38
129
  super(response, SizedQueue.new(10))
39
130
  end
40
131
 
@@ -45,17 +136,63 @@ module ActionController
45
136
  end
46
137
 
47
138
  super
139
+
140
+ unless connected?
141
+ @buf.clear
142
+
143
+ unless @ignore_disconnect
144
+ # Raise ClientDisconnected, which is a RuntimeError (not an
145
+ # IOError), because that's more appropriate for something beyond
146
+ # the developer's control.
147
+ raise ClientDisconnected, "client disconnected"
148
+ end
149
+ end
48
150
  end
49
151
 
50
152
  def each
153
+ @response.sending!
51
154
  while str = @buf.pop
52
155
  yield str
53
156
  end
157
+ @response.sent!
54
158
  end
55
159
 
160
+ # Write a 'close' event to the buffer; the producer/writing thread
161
+ # uses this to notify us that it's finished supplying content.
162
+ #
163
+ # See also #abort.
56
164
  def close
57
- super
58
- @buf.push nil
165
+ synchronize do
166
+ super
167
+ @buf.push nil
168
+ @cv.broadcast
169
+ end
170
+ end
171
+
172
+ # Inform the producer/writing thread that the client has
173
+ # disconnected; the reading thread is no longer interested in
174
+ # anything that's being written.
175
+ #
176
+ # See also #close.
177
+ def abort
178
+ synchronize do
179
+ @aborted = true
180
+ @buf.clear
181
+ end
182
+ end
183
+
184
+ # Is the client still connected and waiting for content?
185
+ #
186
+ # The result of calling `write` when this is `false` is determined
187
+ # by `ignore_disconnect`.
188
+ def connected?
189
+ !@aborted
190
+ end
191
+
192
+ def await_close
193
+ synchronize do
194
+ @cv.wait_until { @closed }
195
+ end
59
196
  end
60
197
 
61
198
  def on_error(&block)
@@ -68,7 +205,7 @@ module ActionController
68
205
  end
69
206
 
70
207
  class Response < ActionDispatch::Response #:nodoc: all
71
- class Header < DelegateClass(Hash)
208
+ class Header < DelegateClass(Hash) # :nodoc:
72
209
  def initialize(response, header)
73
210
  @response = response
74
211
  super(header)
@@ -91,12 +228,20 @@ module ActionController
91
228
  end
92
229
  end
93
230
 
94
- def commit!
95
- headers.freeze
231
+ private
232
+
233
+ def before_committed
96
234
  super
235
+ jar = request.cookie_jar
236
+ # The response can be committed multiple times
237
+ jar.write self unless committed?
97
238
  end
98
239
 
99
- private
240
+ def before_sending
241
+ super
242
+ request.cookie_jar.commit!
243
+ headers.freeze
244
+ end
100
245
 
101
246
  def build_buffer(response, body)
102
247
  buf = Live::Buffer.new response
@@ -117,6 +262,7 @@ module ActionController
117
262
  t1 = Thread.current
118
263
  locals = t1.keys.map { |key| [key, t1[key]] }
119
264
 
265
+ error = nil
120
266
  # This processes the action in a child thread. It lets us return the
121
267
  # response code and headers back up the rack stack, and still process
122
268
  # the body in parallel with sending data to the client
@@ -131,14 +277,18 @@ module ActionController
131
277
  begin
132
278
  super(name)
133
279
  rescue => e
134
- begin
135
- @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
136
- @_response.stream.call_on_error
137
- rescue => exception
138
- log_error(exception)
139
- ensure
140
- log_error(e)
141
- @_response.stream.close
280
+ if @_response.committed?
281
+ begin
282
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
283
+ @_response.stream.call_on_error
284
+ rescue => exception
285
+ log_error(exception)
286
+ ensure
287
+ log_error(e)
288
+ @_response.stream.close
289
+ end
290
+ else
291
+ error = e
142
292
  end
143
293
  ensure
144
294
  @_response.commit!
@@ -146,21 +296,24 @@ module ActionController
146
296
  }
147
297
 
148
298
  @_response.await_commit
299
+ raise error if error
149
300
  end
150
301
 
151
302
  def log_error(exception)
152
303
  logger = ActionController::Base.logger
153
304
  return unless logger
154
305
 
155
- message = "\n#{exception.class} (#{exception.message}):\n"
156
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
157
- message << " " << exception.backtrace.join("\n ")
158
- logger.fatal("#{message}\n\n")
306
+ logger.fatal do
307
+ message = "\n#{exception.class} (#{exception.message}):\n"
308
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
309
+ message << " " << exception.backtrace.join("\n ")
310
+ "#{message}\n\n"
311
+ end
159
312
  end
160
313
 
161
314
  def response_body=(body)
162
315
  super
163
- response.stream.close if response
316
+ response.close if response
164
317
  end
165
318
 
166
319
  def set_response!(request)