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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +402 -1173
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -7
- data/lib/abstract_controller/base.rb +39 -7
- data/lib/abstract_controller/callbacks.rb +32 -53
- data/lib/abstract_controller/collector.rb +11 -1
- data/lib/abstract_controller/helpers.rb +26 -16
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
- data/lib/abstract_controller/rendering.rb +57 -127
- data/lib/abstract_controller/url_for.rb +1 -1
- data/lib/abstract_controller.rb +1 -2
- data/lib/action_controller/base.rb +19 -10
- data/lib/action_controller/caching/fragments.rb +7 -1
- data/lib/action_controller/caching.rb +2 -12
- data/lib/action_controller/log_subscriber.rb +29 -20
- data/lib/action_controller/metal/conditional_get.rb +37 -12
- data/lib/action_controller/metal/data_streaming.rb +1 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
- data/lib/action_controller/metal/exceptions.rb +1 -1
- data/lib/action_controller/metal/flash.rb +17 -0
- data/lib/action_controller/metal/force_ssl.rb +2 -2
- data/lib/action_controller/metal/head.rb +8 -6
- data/lib/action_controller/metal/helpers.rb +6 -2
- data/lib/action_controller/metal/http_authentication.rb +45 -23
- data/lib/action_controller/metal/instrumentation.rb +9 -6
- data/lib/action_controller/metal/live.rb +173 -20
- data/lib/action_controller/metal/mime_responds.rb +127 -232
- data/lib/action_controller/metal/params_wrapper.rb +16 -9
- data/lib/action_controller/metal/rack_delegation.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +34 -26
- data/lib/action_controller/metal/renderers.rb +39 -12
- data/lib/action_controller/metal/rendering.rb +41 -14
- data/lib/action_controller/metal/request_forgery_protection.rb +147 -19
- data/lib/action_controller/metal/streaming.rb +19 -21
- data/lib/action_controller/metal/strong_parameters.rb +166 -22
- data/lib/action_controller/metal/testing.rb +0 -1
- data/lib/action_controller/metal/url_for.rb +11 -12
- data/lib/action_controller/metal.rb +14 -8
- data/lib/action_controller/model_naming.rb +1 -1
- data/lib/action_controller/railtie.rb +5 -1
- data/lib/action_controller/test_case.rb +160 -94
- data/lib/action_controller.rb +2 -18
- data/lib/action_dispatch/http/cache.rb +5 -4
- data/lib/action_dispatch/http/filter_parameters.rb +2 -2
- data/lib/action_dispatch/http/filter_redirect.rb +5 -4
- data/lib/action_dispatch/http/headers.rb +46 -10
- data/lib/action_dispatch/http/mime_negotiation.rb +31 -4
- data/lib/action_dispatch/http/mime_type.rb +25 -26
- data/lib/action_dispatch/http/mime_types.rb +1 -0
- data/lib/action_dispatch/http/parameter_filter.rb +1 -1
- data/lib/action_dispatch/http/parameters.rb +25 -41
- data/lib/action_dispatch/http/request.rb +49 -32
- data/lib/action_dispatch/http/response.rb +127 -25
- data/lib/action_dispatch/http/upload.rb +9 -21
- data/lib/action_dispatch/http/url.rb +97 -70
- data/lib/action_dispatch/journey/formatter.rb +35 -19
- data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -33
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
- data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
- data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
- data/lib/action_dispatch/journey/nodes/node.rb +4 -0
- data/lib/action_dispatch/journey/parser.rb +51 -59
- data/lib/action_dispatch/journey/parser.y +12 -10
- data/lib/action_dispatch/journey/path/pattern.rb +16 -19
- data/lib/action_dispatch/journey/route.rb +8 -19
- data/lib/action_dispatch/journey/router/strexp.rb +9 -6
- data/lib/action_dispatch/journey/router/utils.rb +54 -18
- data/lib/action_dispatch/journey/router.rb +53 -75
- data/lib/action_dispatch/journey/routes.rb +4 -0
- data/lib/action_dispatch/journey/scanner.rb +5 -5
- data/lib/action_dispatch/journey/visitors.rb +81 -60
- data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
- data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
- data/lib/action_dispatch/middleware/callbacks.rb +7 -7
- data/lib/action_dispatch/middleware/cookies.rb +119 -43
- data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -13
- data/lib/action_dispatch/middleware/exception_wrapper.rb +60 -20
- data/lib/action_dispatch/middleware/flash.rb +37 -24
- data/lib/action_dispatch/middleware/params_parser.rb +2 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
- data/lib/action_dispatch/middleware/reloader.rb +11 -2
- data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +8 -7
- data/lib/action_dispatch/middleware/show_exceptions.rb +6 -2
- data/lib/action_dispatch/middleware/ssl.rb +10 -7
- data/lib/action_dispatch/middleware/static.rb +79 -23
- data/lib/action_dispatch/middleware/templates/rescues/{_request_and_response.erb → _request_and_response.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/{routing_error.erb → routing_error.html.erb} +3 -1
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/{unknown_action.erb → unknown_action.html.erb} +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
- data/lib/action_dispatch/railtie.rb +5 -2
- data/lib/action_dispatch/request/session.rb +12 -0
- data/lib/action_dispatch/request/utils.rb +35 -0
- data/lib/action_dispatch/routing/endpoint.rb +10 -0
- data/lib/action_dispatch/routing/inspector.rb +11 -17
- data/lib/action_dispatch/routing/mapper.rb +519 -312
- data/lib/action_dispatch/routing/polymorphic_routes.rb +204 -79
- data/lib/action_dispatch/routing/redirection.rb +51 -26
- data/lib/action_dispatch/routing/route_set.rb +331 -206
- data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
- data/lib/action_dispatch/routing/url_for.rb +19 -5
- data/lib/action_dispatch/routing.rb +9 -6
- data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
- data/lib/action_dispatch/testing/assertions/response.rb +9 -15
- data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
- data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
- data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
- data/lib/action_dispatch/testing/assertions.rb +11 -7
- data/lib/action_dispatch/testing/integration.rb +31 -29
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +1 -5
- data/lib/action_dispatch.rb +5 -8
- data/lib/action_pack/gem_version.rb +15 -0
- data/lib/action_pack/version.rb +4 -7
- data/lib/action_pack.rb +1 -1
- metadata +77 -159
- data/lib/abstract_controller/layouts.rb +0 -423
- data/lib/abstract_controller/view_paths.rb +0 -96
- data/lib/action_controller/deprecated/integration_test.rb +0 -5
- data/lib/action_controller/deprecated.rb +0 -7
- data/lib/action_controller/metal/responder.rb +0 -287
- data/lib/action_controller/record_identifier.rb +0 -31
- data/lib/action_controller/vendor/html-scanner.rb +0 -5
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -24
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -43
- data/lib/action_view/base.rb +0 -201
- data/lib/action_view/buffers.rb +0 -49
- data/lib/action_view/context.rb +0 -36
- data/lib/action_view/dependency_tracker.rb +0 -93
- data/lib/action_view/digestor.rb +0 -113
- data/lib/action_view/flows.rb +0 -76
- data/lib/action_view/helpers/active_model_helper.rb +0 -49
- data/lib/action_view/helpers/asset_tag_helper.rb +0 -320
- data/lib/action_view/helpers/asset_url_helper.rb +0 -355
- data/lib/action_view/helpers/atom_feed_helper.rb +0 -203
- data/lib/action_view/helpers/cache_helper.rb +0 -196
- data/lib/action_view/helpers/capture_helper.rb +0 -216
- data/lib/action_view/helpers/controller_helper.rb +0 -25
- data/lib/action_view/helpers/csrf_helper.rb +0 -30
- data/lib/action_view/helpers/date_helper.rb +0 -1083
- data/lib/action_view/helpers/debug_helper.rb +0 -39
- data/lib/action_view/helpers/form_helper.rb +0 -1880
- data/lib/action_view/helpers/form_options_helper.rb +0 -838
- data/lib/action_view/helpers/form_tag_helper.rb +0 -785
- data/lib/action_view/helpers/javascript_helper.rb +0 -117
- data/lib/action_view/helpers/number_helper.rb +0 -441
- data/lib/action_view/helpers/output_safety_helper.rb +0 -38
- data/lib/action_view/helpers/record_tag_helper.rb +0 -106
- data/lib/action_view/helpers/rendering_helper.rb +0 -90
- data/lib/action_view/helpers/sanitize_helper.rb +0 -256
- data/lib/action_view/helpers/tag_helper.rb +0 -173
- data/lib/action_view/helpers/tags/base.rb +0 -148
- data/lib/action_view/helpers/tags/check_box.rb +0 -64
- data/lib/action_view/helpers/tags/checkable.rb +0 -16
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -44
- data/lib/action_view/helpers/tags/collection_helpers.rb +0 -84
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -36
- data/lib/action_view/helpers/tags/collection_select.rb +0 -28
- data/lib/action_view/helpers/tags/color_field.rb +0 -25
- data/lib/action_view/helpers/tags/date_field.rb +0 -13
- data/lib/action_view/helpers/tags/date_select.rb +0 -72
- data/lib/action_view/helpers/tags/datetime_field.rb +0 -22
- data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -19
- data/lib/action_view/helpers/tags/datetime_select.rb +0 -8
- data/lib/action_view/helpers/tags/email_field.rb +0 -8
- data/lib/action_view/helpers/tags/file_field.rb +0 -8
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +0 -29
- data/lib/action_view/helpers/tags/hidden_field.rb +0 -8
- data/lib/action_view/helpers/tags/label.rb +0 -66
- data/lib/action_view/helpers/tags/month_field.rb +0 -13
- data/lib/action_view/helpers/tags/number_field.rb +0 -18
- data/lib/action_view/helpers/tags/password_field.rb +0 -12
- data/lib/action_view/helpers/tags/radio_button.rb +0 -31
- data/lib/action_view/helpers/tags/range_field.rb +0 -8
- data/lib/action_view/helpers/tags/search_field.rb +0 -24
- data/lib/action_view/helpers/tags/select.rb +0 -40
- data/lib/action_view/helpers/tags/tel_field.rb +0 -8
- data/lib/action_view/helpers/tags/text_area.rb +0 -18
- data/lib/action_view/helpers/tags/text_field.rb +0 -29
- data/lib/action_view/helpers/tags/time_field.rb +0 -13
- data/lib/action_view/helpers/tags/time_select.rb +0 -8
- data/lib/action_view/helpers/tags/time_zone_select.rb +0 -20
- data/lib/action_view/helpers/tags/url_field.rb +0 -8
- data/lib/action_view/helpers/tags/week_field.rb +0 -13
- data/lib/action_view/helpers/tags.rb +0 -39
- data/lib/action_view/helpers/text_helper.rb +0 -443
- data/lib/action_view/helpers/translation_helper.rb +0 -107
- data/lib/action_view/helpers/url_helper.rb +0 -635
- data/lib/action_view/helpers.rb +0 -58
- data/lib/action_view/locale/en.yml +0 -56
- data/lib/action_view/log_subscriber.rb +0 -30
- data/lib/action_view/lookup_context.rb +0 -241
- data/lib/action_view/model_naming.rb +0 -12
- data/lib/action_view/path_set.rb +0 -77
- data/lib/action_view/railtie.rb +0 -43
- data/lib/action_view/record_identifier.rb +0 -84
- data/lib/action_view/renderer/abstract_renderer.rb +0 -47
- data/lib/action_view/renderer/partial_renderer.rb +0 -492
- data/lib/action_view/renderer/renderer.rb +0 -50
- data/lib/action_view/renderer/streaming_template_renderer.rb +0 -103
- data/lib/action_view/renderer/template_renderer.rb +0 -96
- data/lib/action_view/routing_url_for.rb +0 -107
- data/lib/action_view/tasks/dependencies.rake +0 -17
- data/lib/action_view/template/error.rb +0 -138
- data/lib/action_view/template/handlers/builder.rb +0 -26
- data/lib/action_view/template/handlers/erb.rb +0 -146
- data/lib/action_view/template/handlers/raw.rb +0 -11
- data/lib/action_view/template/handlers.rb +0 -53
- data/lib/action_view/template/resolver.rb +0 -326
- data/lib/action_view/template/text.rb +0 -34
- data/lib/action_view/template/types.rb +0 -57
- data/lib/action_view/template.rb +0 -339
- data/lib/action_view/test_case.rb +0 -270
- data/lib/action_view/testing/resolvers.rb +0 -50
- data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
- data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_view/vendor/html-scanner.rb +0 -20
- 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
|
15
|
+
# render plain: "Everyone can see me!"
|
15
16
|
# end
|
16
17
|
#
|
17
18
|
# def edit
|
18
|
-
# render
|
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
|
-
#
|
57
|
-
#
|
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
|
-
|
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
|
-
|
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(
|
106
|
+
decode_credentials(request).split(':', 2)
|
100
107
|
end
|
101
108
|
|
102
109
|
def decode_credentials(request)
|
103
|
-
::Base64.decode64(request
|
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
|
145
|
+
# render plain: "Everyone can see me!"
|
131
146
|
# end
|
132
147
|
#
|
133
148
|
# def edit
|
134
|
-
# render
|
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
|
339
|
+
# render plain: "Everyone can see me!"
|
325
340
|
# end
|
326
341
|
#
|
327
342
|
# def edit
|
328
|
-
# render
|
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
|
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
|
469
|
+
# This removes the <tt>"</tt> characters wrapping the value.
|
454
470
|
def rewrite_param_values(array_params)
|
455
|
-
array_params.each { |param| param.
|
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
|
460
|
-
#
|
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(
|
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 = ["
|
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.
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
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 =
|
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
|
-
|
58
|
-
|
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
|
-
|
95
|
-
|
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
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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.
|
316
|
+
response.close if response
|
164
317
|
end
|
165
318
|
|
166
319
|
def set_response!(request)
|