actionpack 3.2.19 → 4.2.11.3
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 +7 -0
- data/CHANGELOG.md +412 -503
- data/MIT-LICENSE +1 -1
- data/README.rdoc +11 -294
- data/lib/abstract_controller/asset_paths.rb +2 -2
- data/lib/abstract_controller/base.rb +52 -18
- data/lib/abstract_controller/callbacks.rb +87 -89
- data/lib/abstract_controller/collector.rb +17 -3
- data/lib/abstract_controller/helpers.rb +41 -14
- data/lib/abstract_controller/logger.rb +1 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
- data/lib/abstract_controller/rendering.rb +65 -118
- data/lib/abstract_controller/translation.rb +16 -1
- data/lib/abstract_controller/url_for.rb +7 -7
- data/lib/abstract_controller.rb +2 -10
- data/lib/action_controller/base.rb +61 -28
- data/lib/action_controller/caching/fragments.rb +30 -54
- data/lib/action_controller/caching.rb +38 -35
- data/lib/action_controller/log_subscriber.rb +35 -18
- data/lib/action_controller/metal/conditional_get.rb +103 -34
- data/lib/action_controller/metal/data_streaming.rb +20 -26
- data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
- data/lib/action_controller/metal/exceptions.rb +19 -6
- data/lib/action_controller/metal/flash.rb +41 -9
- data/lib/action_controller/metal/force_ssl.rb +70 -12
- data/lib/action_controller/metal/head.rb +30 -7
- data/lib/action_controller/metal/helpers.rb +11 -11
- data/lib/action_controller/metal/hide_actions.rb +0 -1
- data/lib/action_controller/metal/http_authentication.rb +140 -94
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +11 -7
- data/lib/action_controller/metal/live.rb +328 -0
- data/lib/action_controller/metal/mime_responds.rb +161 -152
- data/lib/action_controller/metal/params_wrapper.rb +126 -81
- data/lib/action_controller/metal/rack_delegation.rb +10 -4
- data/lib/action_controller/metal/redirecting.rb +44 -41
- data/lib/action_controller/metal/renderers.rb +48 -19
- data/lib/action_controller/metal/rendering.rb +46 -11
- data/lib/action_controller/metal/request_forgery_protection.rb +250 -29
- data/lib/action_controller/metal/streaming.rb +30 -38
- data/lib/action_controller/metal/strong_parameters.rb +669 -0
- data/lib/action_controller/metal/testing.rb +12 -18
- data/lib/action_controller/metal/url_for.rb +31 -29
- data/lib/action_controller/metal.rb +31 -40
- data/lib/action_controller/model_naming.rb +12 -0
- data/lib/action_controller/railtie.rb +38 -18
- data/lib/action_controller/railties/helpers.rb +22 -0
- data/lib/action_controller/test_case.rb +359 -173
- data/lib/action_controller.rb +9 -16
- data/lib/action_dispatch/http/cache.rb +64 -11
- data/lib/action_dispatch/http/filter_parameters.rb +20 -10
- data/lib/action_dispatch/http/filter_redirect.rb +38 -0
- data/lib/action_dispatch/http/headers.rb +85 -17
- data/lib/action_dispatch/http/mime_negotiation.rb +55 -5
- data/lib/action_dispatch/http/mime_type.rb +167 -114
- data/lib/action_dispatch/http/mime_types.rb +2 -1
- data/lib/action_dispatch/http/parameter_filter.rb +44 -46
- data/lib/action_dispatch/http/parameters.rb +30 -46
- data/lib/action_dispatch/http/rack_cache.rb +2 -3
- data/lib/action_dispatch/http/request.rb +108 -45
- data/lib/action_dispatch/http/response.rb +247 -48
- data/lib/action_dispatch/http/upload.rb +60 -29
- data/lib/action_dispatch/http/url.rb +135 -45
- data/lib/action_dispatch/journey/backwards.rb +5 -0
- data/lib/action_dispatch/journey/formatter.rb +166 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +47 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +157 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
- data/lib/action_dispatch/journey/nodes/node.rb +128 -0
- data/lib/action_dispatch/journey/parser.rb +198 -0
- data/lib/action_dispatch/journey/parser.y +49 -0
- data/lib/action_dispatch/journey/parser_extras.rb +23 -0
- data/lib/action_dispatch/journey/path/pattern.rb +193 -0
- data/lib/action_dispatch/journey/route.rb +125 -0
- data/lib/action_dispatch/journey/router/strexp.rb +27 -0
- data/lib/action_dispatch/journey/router/utils.rb +93 -0
- data/lib/action_dispatch/journey/router.rb +144 -0
- data/lib/action_dispatch/journey/routes.rb +80 -0
- data/lib/action_dispatch/journey/scanner.rb +61 -0
- data/lib/action_dispatch/journey/visitors.rb +221 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +5 -0
- data/lib/action_dispatch/middleware/callbacks.rb +16 -11
- data/lib/action_dispatch/middleware/cookies.rb +346 -125
- data/lib/action_dispatch/middleware/debug_exceptions.rb +52 -24
- data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -9
- data/lib/action_dispatch/middleware/flash.rb +85 -72
- data/lib/action_dispatch/middleware/params_parser.rb +16 -31
- data/lib/action_dispatch/middleware/public_exceptions.rb +39 -14
- data/lib/action_dispatch/middleware/reloader.rb +16 -7
- data/lib/action_dispatch/middleware/remote_ip.rb +132 -40
- data/lib/action_dispatch/middleware/request_id.rb +3 -7
- data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
- data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +84 -29
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +15 -44
- data/lib/action_dispatch/middleware/ssl.rb +72 -0
- data/lib/action_dispatch/middleware/stack.rb +6 -1
- data/lib/action_dispatch/middleware/static.rb +80 -23
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +34 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +27 -0
- 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.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +133 -5
- 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.html.erb +32 -0
- 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.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
- data/lib/action_dispatch/railtie.rb +19 -6
- data/lib/action_dispatch/request/session.rb +193 -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 +234 -0
- data/lib/action_dispatch/routing/mapper.rb +897 -436
- data/lib/action_dispatch/routing/polymorphic_routes.rb +213 -92
- data/lib/action_dispatch/routing/redirection.rb +97 -37
- data/lib/action_dispatch/routing/route_set.rb +432 -239
- data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
- data/lib/action_dispatch/routing/url_for.rb +63 -34
- data/lib/action_dispatch/routing.rb +57 -89
- data/lib/action_dispatch/testing/assertions/dom.rb +2 -36
- data/lib/action_dispatch/testing/assertions/response.rb +24 -38
- data/lib/action_dispatch/testing/assertions/routing.rb +55 -54
- data/lib/action_dispatch/testing/assertions/selector.rb +2 -434
- data/lib/action_dispatch/testing/assertions/tag.rb +2 -137
- data/lib/action_dispatch/testing/assertions.rb +11 -7
- data/lib/action_dispatch/testing/integration.rb +88 -72
- data/lib/action_dispatch/testing/test_process.rb +9 -6
- data/lib/action_dispatch/testing/test_request.rb +13 -9
- data/lib/action_dispatch/testing/test_response.rb +1 -5
- data/lib/action_dispatch.rb +24 -21
- data/lib/action_pack/gem_version.rb +15 -0
- data/lib/action_pack/version.rb +5 -7
- data/lib/action_pack.rb +1 -1
- metadata +181 -292
- data/lib/abstract_controller/layouts.rb +0 -423
- data/lib/abstract_controller/view_paths.rb +0 -96
- data/lib/action_controller/caching/actions.rb +0 -185
- data/lib/action_controller/caching/pages.rb +0 -187
- data/lib/action_controller/caching/sweeping.rb +0 -97
- data/lib/action_controller/deprecated/integration_test.rb +0 -2
- data/lib/action_controller/deprecated/performance_test.rb +0 -1
- data/lib/action_controller/deprecated.rb +0 -3
- data/lib/action_controller/metal/compatibility.rb +0 -65
- data/lib/action_controller/metal/responder.rb +0 -286
- data/lib/action_controller/metal/session_management.rb +0 -14
- data/lib/action_controller/railties/paths.rb +0 -25
- data/lib/action_controller/record_identifier.rb +0 -85
- data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_controller/vendor/html-scanner.rb +0 -20
- data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
- data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
- data/lib/action_dispatch/middleware/head.rb +0 -18
- data/lib/action_dispatch/middleware/rescue.rb +0 -26
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
- data/lib/action_dispatch/testing/performance_test.rb +0 -10
- data/lib/action_view/asset_paths.rb +0 -142
- data/lib/action_view/base.rb +0 -220
- data/lib/action_view/buffers.rb +0 -43
- data/lib/action_view/context.rb +0 -36
- data/lib/action_view/flows.rb +0 -79
- data/lib/action_view/helpers/active_model_helper.rb +0 -50
- data/lib/action_view/helpers/asset_paths.rb +0 -7
- data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
- data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
- data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
- data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
- data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
- data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
- data/lib/action_view/helpers/cache_helper.rb +0 -64
- data/lib/action_view/helpers/capture_helper.rb +0 -203
- data/lib/action_view/helpers/controller_helper.rb +0 -25
- data/lib/action_view/helpers/csrf_helper.rb +0 -32
- data/lib/action_view/helpers/date_helper.rb +0 -1062
- data/lib/action_view/helpers/debug_helper.rb +0 -40
- data/lib/action_view/helpers/form_helper.rb +0 -1486
- data/lib/action_view/helpers/form_options_helper.rb +0 -658
- data/lib/action_view/helpers/form_tag_helper.rb +0 -685
- data/lib/action_view/helpers/javascript_helper.rb +0 -110
- data/lib/action_view/helpers/number_helper.rb +0 -622
- data/lib/action_view/helpers/output_safety_helper.rb +0 -38
- data/lib/action_view/helpers/record_tag_helper.rb +0 -111
- data/lib/action_view/helpers/rendering_helper.rb +0 -90
- data/lib/action_view/helpers/sanitize_helper.rb +0 -259
- data/lib/action_view/helpers/tag_helper.rb +0 -160
- data/lib/action_view/helpers/text_helper.rb +0 -426
- data/lib/action_view/helpers/translation_helper.rb +0 -91
- data/lib/action_view/helpers/url_helper.rb +0 -693
- data/lib/action_view/helpers.rb +0 -60
- data/lib/action_view/locale/en.yml +0 -160
- data/lib/action_view/log_subscriber.rb +0 -28
- data/lib/action_view/lookup_context.rb +0 -254
- data/lib/action_view/path_set.rb +0 -89
- data/lib/action_view/railtie.rb +0 -55
- data/lib/action_view/renderer/abstract_renderer.rb +0 -41
- data/lib/action_view/renderer/partial_renderer.rb +0 -415
- data/lib/action_view/renderer/renderer.rb +0 -54
- data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
- data/lib/action_view/renderer/template_renderer.rb +0 -94
- data/lib/action_view/template/error.rb +0 -128
- data/lib/action_view/template/handlers/builder.rb +0 -26
- data/lib/action_view/template/handlers/erb.rb +0 -125
- data/lib/action_view/template/handlers.rb +0 -50
- data/lib/action_view/template/resolver.rb +0 -272
- data/lib/action_view/template/text.rb +0 -30
- data/lib/action_view/template.rb +0 -337
- data/lib/action_view/test_case.rb +0 -245
- data/lib/action_view/testing/resolvers.rb +0 -50
- data/lib/action_view.rb +0 -84
- data/lib/sprockets/assets.rake +0 -99
- data/lib/sprockets/bootstrap.rb +0 -37
- data/lib/sprockets/compressors.rb +0 -83
- data/lib/sprockets/helpers/isolated_helper.rb +0 -13
- data/lib/sprockets/helpers/rails_helper.rb +0 -182
- data/lib/sprockets/helpers.rb +0 -6
- data/lib/sprockets/railtie.rb +0 -62
- data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,9 +1,12 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
1
|
require 'active_support/core_ext/hash/keys'
|
3
2
|
require 'active_support/core_ext/module/attribute_accessors'
|
3
|
+
require 'active_support/core_ext/object/blank'
|
4
|
+
require 'active_support/key_generator'
|
5
|
+
require 'active_support/message_verifier'
|
6
|
+
require 'active_support/json'
|
4
7
|
|
5
8
|
module ActionDispatch
|
6
|
-
class Request
|
9
|
+
class Request < Rack::Request
|
7
10
|
def cookie_jar
|
8
11
|
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
|
9
12
|
end
|
@@ -15,21 +18,21 @@ module ActionDispatch
|
|
15
18
|
# being written will be sent out with the response. Reading a cookie does not get
|
16
19
|
# the cookie object itself back, just the value it holds.
|
17
20
|
#
|
18
|
-
# Examples
|
21
|
+
# Examples of writing:
|
19
22
|
#
|
20
23
|
# # Sets a simple session cookie.
|
21
24
|
# # This cookie will be deleted when the user's browser is closed.
|
22
25
|
# cookies[:user_name] = "david"
|
23
26
|
#
|
24
|
-
# #
|
25
|
-
# cookies[:lat_lon] = [47.68, -122.37]
|
27
|
+
# # Cookie values are String based. Other data types need to be serialized.
|
28
|
+
# cookies[:lat_lon] = JSON.generate([47.68, -122.37])
|
26
29
|
#
|
27
30
|
# # Sets a cookie that expires in 1 hour.
|
28
|
-
# cookies[:login] = { :
|
31
|
+
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
|
29
32
|
#
|
30
|
-
# # Sets a signed cookie, which prevents
|
31
|
-
# # The cookie is signed by your app's
|
32
|
-
# #
|
33
|
+
# # Sets a signed cookie, which prevents users from tampering with its value.
|
34
|
+
# # The cookie is signed by your app's `secrets.secret_key_base` value.
|
35
|
+
# # It can be read using the signed method `cookies.signed[:name]`
|
33
36
|
# cookies.signed[:user_id] = current_user.id
|
34
37
|
#
|
35
38
|
# # Sets a "permanent" cookie (which expires in 20 years from now).
|
@@ -38,11 +41,12 @@ module ActionDispatch
|
|
38
41
|
# # You can also chain these methods:
|
39
42
|
# cookies.permanent.signed[:login] = "XJ-122"
|
40
43
|
#
|
41
|
-
# Examples
|
44
|
+
# Examples of reading:
|
42
45
|
#
|
43
|
-
# cookies[:user_name]
|
44
|
-
# cookies.size
|
45
|
-
# cookies[:lat_lon]
|
46
|
+
# cookies[:user_name] # => "david"
|
47
|
+
# cookies.size # => 2
|
48
|
+
# JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
|
49
|
+
# cookies.signed[:login] # => "XJ-122"
|
46
50
|
#
|
47
51
|
# Example for deleting:
|
48
52
|
#
|
@@ -50,43 +54,150 @@ module ActionDispatch
|
|
50
54
|
#
|
51
55
|
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
|
52
56
|
#
|
53
|
-
# cookies[:
|
54
|
-
# :
|
55
|
-
# :
|
56
|
-
# :
|
57
|
+
# cookies[:name] = {
|
58
|
+
# value: 'a yummy cookie',
|
59
|
+
# expires: 1.year.from_now,
|
60
|
+
# domain: 'domain.com'
|
57
61
|
# }
|
58
62
|
#
|
59
|
-
# cookies.delete(:
|
63
|
+
# cookies.delete(:name, domain: 'domain.com')
|
60
64
|
#
|
61
65
|
# The option symbols for setting cookies are:
|
62
66
|
#
|
63
|
-
# * <tt>:value</tt> - The cookie's value
|
67
|
+
# * <tt>:value</tt> - The cookie's value.
|
64
68
|
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
|
65
69
|
# of the application.
|
66
70
|
# * <tt>:domain</tt> - The domain for which this cookie applies so you can
|
67
71
|
# restrict to the domain level. If you use a schema like www.example.com
|
68
72
|
# and want to share session with user.example.com set <tt>:domain</tt>
|
69
73
|
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
|
70
|
-
# <tt>:all</tt> again when deleting
|
74
|
+
# <tt>:all</tt> or <tt>Array</tt> again when deleting cookies.
|
71
75
|
#
|
72
|
-
# :
|
73
|
-
# :
|
74
|
-
#
|
76
|
+
# domain: nil # Does not sets cookie domain. (default)
|
77
|
+
# domain: :all # Allow the cookie for the top most level
|
78
|
+
# # domain and subdomains.
|
79
|
+
# domain: %w(.example.com .example.org) # Allow the cookie
|
80
|
+
# # for concrete domain names.
|
75
81
|
#
|
76
82
|
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
|
77
|
-
# * <tt>:secure</tt> - Whether this cookie is
|
83
|
+
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
|
78
84
|
# Default is +false+.
|
79
85
|
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
|
80
86
|
# only HTTP. Defaults to +false+.
|
81
87
|
class Cookies
|
82
|
-
HTTP_HEADER
|
83
|
-
|
88
|
+
HTTP_HEADER = "Set-Cookie".freeze
|
89
|
+
GENERATOR_KEY = "action_dispatch.key_generator".freeze
|
90
|
+
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
|
91
|
+
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
|
92
|
+
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
|
93
|
+
SECRET_TOKEN = "action_dispatch.secret_token".freeze
|
94
|
+
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
|
95
|
+
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
|
96
|
+
COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
|
97
|
+
|
98
|
+
# Cookies can typically store 4096 bytes.
|
99
|
+
MAX_COOKIE_SIZE = 4096
|
84
100
|
|
85
101
|
# Raised when storing more than 4K of session data.
|
86
|
-
|
102
|
+
CookieOverflow = Class.new StandardError
|
103
|
+
|
104
|
+
# Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
|
105
|
+
module ChainedCookieJars
|
106
|
+
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
|
107
|
+
#
|
108
|
+
# cookies.permanent[:prefers_open_id] = true
|
109
|
+
# # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
110
|
+
#
|
111
|
+
# This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
|
112
|
+
#
|
113
|
+
# This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
|
114
|
+
#
|
115
|
+
# cookies.permanent.signed[:remember_me] = current_user.id
|
116
|
+
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
117
|
+
def permanent
|
118
|
+
@permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
|
122
|
+
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
123
|
+
# cookie was tampered with by the user (or a 3rd party), nil will be returned.
|
124
|
+
#
|
125
|
+
# If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
|
126
|
+
# legacy cookies signed with the old key generator will be transparently upgraded.
|
127
|
+
#
|
128
|
+
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
|
129
|
+
#
|
130
|
+
# Example:
|
131
|
+
#
|
132
|
+
# cookies.signed[:discount] = 45
|
133
|
+
# # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
|
134
|
+
#
|
135
|
+
# cookies.signed[:discount] # => 45
|
136
|
+
def signed
|
137
|
+
@signed ||=
|
138
|
+
if @options[:upgrade_legacy_signed_cookies]
|
139
|
+
UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
|
140
|
+
else
|
141
|
+
SignedCookieJar.new(self, @key_generator, @options)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
|
146
|
+
# If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
|
147
|
+
#
|
148
|
+
# If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
|
149
|
+
# legacy cookies signed with the old key generator will be transparently upgraded.
|
150
|
+
#
|
151
|
+
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
|
152
|
+
#
|
153
|
+
# Example:
|
154
|
+
#
|
155
|
+
# cookies.encrypted[:discount] = 45
|
156
|
+
# # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
|
157
|
+
#
|
158
|
+
# cookies.encrypted[:discount] # => 45
|
159
|
+
def encrypted
|
160
|
+
@encrypted ||=
|
161
|
+
if @options[:upgrade_legacy_signed_cookies]
|
162
|
+
UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
|
163
|
+
else
|
164
|
+
EncryptedCookieJar.new(self, @key_generator, @options)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
|
169
|
+
# Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
|
170
|
+
def signed_or_encrypted
|
171
|
+
@signed_or_encrypted ||=
|
172
|
+
if @options[:secret_key_base].present?
|
173
|
+
encrypted
|
174
|
+
else
|
175
|
+
signed
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
|
181
|
+
# to the Message{Encryptor,Verifier} allows us to handle the
|
182
|
+
# (de)serialization step within the cookie jar, which gives us the
|
183
|
+
# opportunity to detect and migrate legacy cookies.
|
184
|
+
module VerifyAndUpgradeLegacySignedMessage # :nodoc:
|
185
|
+
def initialize(*args)
|
186
|
+
super
|
187
|
+
@legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
|
188
|
+
end
|
189
|
+
|
190
|
+
def verify_and_upgrade_legacy_signed_message(name, signed_message)
|
191
|
+
deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value|
|
192
|
+
self[name] = { value: value }
|
193
|
+
end
|
194
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
end
|
87
198
|
|
88
199
|
class CookieJar #:nodoc:
|
89
|
-
include Enumerable
|
200
|
+
include Enumerable, ChainedCookieJars
|
90
201
|
|
91
202
|
# This regular expression is used to split the levels of a domain.
|
92
203
|
# The top level domain can be any string without a period or
|
@@ -102,24 +213,48 @@ module ActionDispatch
|
|
102
213
|
# $& => example.local
|
103
214
|
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
|
104
215
|
|
216
|
+
def self.options_for_env(env) #:nodoc:
|
217
|
+
{ signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
|
218
|
+
encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
|
219
|
+
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
|
220
|
+
secret_token: env[SECRET_TOKEN],
|
221
|
+
secret_key_base: env[SECRET_KEY_BASE],
|
222
|
+
upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
|
223
|
+
serializer: env[COOKIES_SERIALIZER],
|
224
|
+
digest: env[COOKIES_DIGEST]
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
105
228
|
def self.build(request)
|
106
|
-
|
229
|
+
env = request.env
|
230
|
+
key_generator = env[GENERATOR_KEY]
|
231
|
+
options = options_for_env env
|
232
|
+
|
107
233
|
host = request.host
|
108
234
|
secure = request.ssl?
|
109
235
|
|
110
|
-
new(
|
236
|
+
new(key_generator, host, secure, options).tap do |hash|
|
111
237
|
hash.update(request.cookies)
|
112
238
|
end
|
113
239
|
end
|
114
240
|
|
115
|
-
def initialize(
|
116
|
-
@
|
241
|
+
def initialize(key_generator, host = nil, secure = false, options = {})
|
242
|
+
@key_generator = key_generator
|
117
243
|
@set_cookies = {}
|
118
244
|
@delete_cookies = {}
|
119
245
|
@host = host
|
120
246
|
@secure = secure
|
121
|
-
@
|
247
|
+
@options = options
|
122
248
|
@cookies = {}
|
249
|
+
@committed = false
|
250
|
+
end
|
251
|
+
|
252
|
+
def committed?; @committed; end
|
253
|
+
|
254
|
+
def commit!
|
255
|
+
@committed = true
|
256
|
+
@set_cookies.freeze
|
257
|
+
@delete_cookies.freeze
|
123
258
|
end
|
124
259
|
|
125
260
|
def each(&block)
|
@@ -131,6 +266,10 @@ module ActionDispatch
|
|
131
266
|
@cookies[name.to_s]
|
132
267
|
end
|
133
268
|
|
269
|
+
def fetch(name, *args, &block)
|
270
|
+
@cookies.fetch(name.to_s, *args, &block)
|
271
|
+
end
|
272
|
+
|
134
273
|
def key?(name)
|
135
274
|
@cookies.key?(name.to_s)
|
136
275
|
end
|
@@ -155,13 +294,13 @@ module ActionDispatch
|
|
155
294
|
end
|
156
295
|
elsif options[:domain].is_a? Array
|
157
296
|
# if host matches one of the supplied domains without a dot in front of it
|
158
|
-
options[:domain] = options[:domain].find {|domain| @host.include? domain
|
297
|
+
options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
|
159
298
|
end
|
160
299
|
end
|
161
300
|
|
162
|
-
# Sets the cookie named +name+. The second argument may be the
|
163
|
-
# value
|
164
|
-
def []=(
|
301
|
+
# Sets the cookie named +name+. The second argument may be the cookie's
|
302
|
+
# value or a hash of options as documented above.
|
303
|
+
def []=(name, options)
|
165
304
|
if options.is_a?(Hash)
|
166
305
|
options.symbolize_keys!
|
167
306
|
value = options[:value]
|
@@ -172,91 +311,76 @@ module ActionDispatch
|
|
172
311
|
|
173
312
|
handle_options(options)
|
174
313
|
|
175
|
-
if @cookies[
|
176
|
-
@cookies[
|
177
|
-
@set_cookies[
|
178
|
-
@delete_cookies.delete(
|
314
|
+
if @cookies[name.to_s] != value || options[:expires]
|
315
|
+
@cookies[name.to_s] = value
|
316
|
+
@set_cookies[name.to_s] = options
|
317
|
+
@delete_cookies.delete(name.to_s)
|
179
318
|
end
|
180
319
|
|
181
320
|
value
|
182
321
|
end
|
183
322
|
|
184
323
|
# Removes the cookie on the client machine by setting the value to an empty string
|
185
|
-
# and
|
324
|
+
# and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
|
186
325
|
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
|
187
|
-
def delete(
|
188
|
-
|
326
|
+
def delete(name, options = {})
|
327
|
+
return unless @cookies.has_key? name.to_s
|
189
328
|
|
329
|
+
options.symbolize_keys!
|
190
330
|
handle_options(options)
|
191
331
|
|
192
|
-
value = @cookies.delete(
|
193
|
-
@delete_cookies[
|
332
|
+
value = @cookies.delete(name.to_s)
|
333
|
+
@delete_cookies[name.to_s] = options
|
194
334
|
value
|
195
335
|
end
|
196
336
|
|
337
|
+
# Whether the given cookie is to be deleted by this CookieJar.
|
338
|
+
# Like <tt>[]=</tt>, you can pass in an options hash to test if a
|
339
|
+
# deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
|
340
|
+
def deleted?(name, options = {})
|
341
|
+
options.symbolize_keys!
|
342
|
+
handle_options(options)
|
343
|
+
@delete_cookies[name.to_s] == options
|
344
|
+
end
|
345
|
+
|
197
346
|
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
|
198
347
|
def clear(options = {})
|
199
348
|
@cookies.each_key{ |k| delete(k, options) }
|
200
349
|
end
|
201
350
|
|
202
|
-
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
|
203
|
-
#
|
204
|
-
# cookies.permanent[:prefers_open_id] = true
|
205
|
-
# # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
206
|
-
#
|
207
|
-
# This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
|
208
|
-
#
|
209
|
-
# This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
|
210
|
-
#
|
211
|
-
# cookies.permanent.signed[:remember_me] = current_user.id
|
212
|
-
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
213
|
-
def permanent
|
214
|
-
@permanent ||= PermanentCookieJar.new(self, @secret)
|
215
|
-
end
|
216
|
-
|
217
|
-
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
|
218
|
-
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
219
|
-
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
|
220
|
-
# be raised.
|
221
|
-
#
|
222
|
-
# This jar requires that you set a suitable secret for the verification on your app's config.secret_token.
|
223
|
-
#
|
224
|
-
# Example:
|
225
|
-
#
|
226
|
-
# cookies.signed[:discount] = 45
|
227
|
-
# # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
|
228
|
-
#
|
229
|
-
# cookies.signed[:discount] # => 45
|
230
|
-
def signed
|
231
|
-
@signed ||= SignedCookieJar.new(self, @secret)
|
232
|
-
end
|
233
|
-
|
234
351
|
def write(headers)
|
235
352
|
@set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
|
236
353
|
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
|
237
354
|
end
|
238
355
|
|
239
356
|
def recycle! #:nodoc:
|
240
|
-
@set_cookies
|
241
|
-
@delete_cookies
|
357
|
+
@set_cookies = {}
|
358
|
+
@delete_cookies = {}
|
242
359
|
end
|
243
360
|
|
244
361
|
mattr_accessor :always_write_cookie
|
245
362
|
self.always_write_cookie = false
|
246
363
|
|
247
364
|
private
|
248
|
-
|
249
365
|
def write_cookie?(cookie)
|
250
366
|
@secure || !cookie[:secure] || always_write_cookie
|
251
367
|
end
|
252
368
|
end
|
253
369
|
|
254
|
-
class PermanentCookieJar
|
255
|
-
|
256
|
-
|
370
|
+
class PermanentCookieJar #:nodoc:
|
371
|
+
include ChainedCookieJars
|
372
|
+
|
373
|
+
def initialize(parent_jar, key_generator, options = {})
|
374
|
+
@parent_jar = parent_jar
|
375
|
+
@key_generator = key_generator
|
376
|
+
@options = options
|
377
|
+
end
|
378
|
+
|
379
|
+
def [](name)
|
380
|
+
@parent_jar[name.to_s]
|
257
381
|
end
|
258
382
|
|
259
|
-
def []=(
|
383
|
+
def []=(name, options)
|
260
384
|
if options.is_a?(Hash)
|
261
385
|
options.symbolize_keys!
|
262
386
|
else
|
@@ -264,70 +388,166 @@ module ActionDispatch
|
|
264
388
|
end
|
265
389
|
|
266
390
|
options[:expires] = 20.years.from_now
|
267
|
-
@parent_jar[
|
391
|
+
@parent_jar[name] = options
|
268
392
|
end
|
393
|
+
end
|
269
394
|
|
270
|
-
|
271
|
-
|
395
|
+
class JsonSerializer # :nodoc:
|
396
|
+
def self.load(value)
|
397
|
+
ActiveSupport::JSON.decode(value)
|
272
398
|
end
|
273
399
|
|
274
|
-
def
|
275
|
-
|
400
|
+
def self.dump(value)
|
401
|
+
ActiveSupport::JSON.encode(value)
|
276
402
|
end
|
277
403
|
end
|
278
404
|
|
279
|
-
|
280
|
-
|
281
|
-
|
405
|
+
module SerializedCookieJars # :nodoc:
|
406
|
+
MARSHAL_SIGNATURE = "\x04\x08".freeze
|
407
|
+
|
408
|
+
protected
|
409
|
+
def needs_migration?(value)
|
410
|
+
@options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
|
411
|
+
end
|
412
|
+
|
413
|
+
def serialize(name, value)
|
414
|
+
serializer.dump(value)
|
415
|
+
end
|
416
|
+
|
417
|
+
def deserialize(name, value)
|
418
|
+
if value
|
419
|
+
if needs_migration?(value)
|
420
|
+
Marshal.load(value).tap do |v|
|
421
|
+
self[name] = { value: v }
|
422
|
+
end
|
423
|
+
else
|
424
|
+
serializer.load(value)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def serializer
|
430
|
+
serializer = @options[:serializer] || :marshal
|
431
|
+
case serializer
|
432
|
+
when :marshal
|
433
|
+
Marshal
|
434
|
+
when :json, :hybrid
|
435
|
+
JsonSerializer
|
436
|
+
else
|
437
|
+
serializer
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def digest
|
442
|
+
@options[:digest] || 'SHA1'
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
class SignedCookieJar #:nodoc:
|
447
|
+
include ChainedCookieJars
|
448
|
+
include SerializedCookieJars
|
282
449
|
|
283
|
-
def initialize(parent_jar,
|
284
|
-
ensure_secret_secure(secret)
|
450
|
+
def initialize(parent_jar, key_generator, options = {})
|
285
451
|
@parent_jar = parent_jar
|
286
|
-
@
|
452
|
+
@options = options
|
453
|
+
secret = key_generator.generate_key(@options[:signed_cookie_salt])
|
454
|
+
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
|
287
455
|
end
|
288
456
|
|
289
457
|
def [](name)
|
290
458
|
if signed_message = @parent_jar[name]
|
291
|
-
|
459
|
+
deserialize name, verify(signed_message)
|
292
460
|
end
|
293
|
-
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
294
|
-
nil
|
295
461
|
end
|
296
462
|
|
297
|
-
def []=(
|
463
|
+
def []=(name, options)
|
298
464
|
if options.is_a?(Hash)
|
299
465
|
options.symbolize_keys!
|
300
|
-
options[:value] = @verifier.generate(options[:value])
|
466
|
+
options[:value] = @verifier.generate(serialize(name, options[:value]))
|
301
467
|
else
|
302
|
-
options = { :value => @verifier.generate(options) }
|
468
|
+
options = { :value => @verifier.generate(serialize(name, options)) }
|
469
|
+
end
|
470
|
+
|
471
|
+
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
472
|
+
@parent_jar[name] = options
|
473
|
+
end
|
474
|
+
|
475
|
+
private
|
476
|
+
def verify(signed_message)
|
477
|
+
@verifier.verify(signed_message)
|
478
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
479
|
+
nil
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
|
484
|
+
# secrets.secret_token and secrets.secret_key_base are both set. It reads
|
485
|
+
# legacy cookies signed with the old dummy key generator and re-saves
|
486
|
+
# them using the new key generator to provide a smooth upgrade path.
|
487
|
+
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
|
488
|
+
include VerifyAndUpgradeLegacySignedMessage
|
489
|
+
|
490
|
+
def [](name)
|
491
|
+
if signed_message = @parent_jar[name]
|
492
|
+
deserialize(name, verify(signed_message)) || verify_and_upgrade_legacy_signed_message(name, signed_message)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
class EncryptedCookieJar #:nodoc:
|
498
|
+
include ChainedCookieJars
|
499
|
+
include SerializedCookieJars
|
500
|
+
|
501
|
+
def initialize(parent_jar, key_generator, options = {})
|
502
|
+
if ActiveSupport::LegacyKeyGenerator === key_generator
|
503
|
+
raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
|
504
|
+
"Read the upgrade documentation to learn more about this new config option."
|
303
505
|
end
|
304
506
|
|
305
|
-
|
306
|
-
@
|
507
|
+
@parent_jar = parent_jar
|
508
|
+
@options = options
|
509
|
+
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])[0, ActiveSupport::MessageEncryptor.key_len]
|
510
|
+
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
|
511
|
+
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
|
307
512
|
end
|
308
513
|
|
309
|
-
def
|
310
|
-
@parent_jar
|
514
|
+
def [](name)
|
515
|
+
if encrypted_message = @parent_jar[name]
|
516
|
+
deserialize name, decrypt_and_verify(encrypted_message)
|
517
|
+
end
|
311
518
|
end
|
312
519
|
|
313
|
-
|
520
|
+
def []=(name, options)
|
521
|
+
if options.is_a?(Hash)
|
522
|
+
options.symbolize_keys!
|
523
|
+
else
|
524
|
+
options = { :value => options }
|
525
|
+
end
|
526
|
+
|
527
|
+
options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
|
314
528
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
529
|
+
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
|
530
|
+
@parent_jar[name] = options
|
531
|
+
end
|
532
|
+
|
533
|
+
private
|
534
|
+
def decrypt_and_verify(encrypted_message)
|
535
|
+
@encryptor.decrypt_and_verify(encrypted_message)
|
536
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
|
537
|
+
nil
|
324
538
|
end
|
539
|
+
end
|
540
|
+
|
541
|
+
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
|
542
|
+
# instead of EncryptedCookieJar if secrets.secret_token and secrets.secret_key_base
|
543
|
+
# are both set. It reads legacy cookies signed with the old dummy key generator and
|
544
|
+
# encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
|
545
|
+
class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
|
546
|
+
include VerifyAndUpgradeLegacySignedMessage
|
325
547
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
"provided, \"#{secret}\", is shorter than the minimum length " +
|
330
|
-
"of #{SECRET_MIN_LENGTH} characters"
|
548
|
+
def [](name)
|
549
|
+
if encrypted_or_signed_message = @parent_jar[name]
|
550
|
+
deserialize(name, decrypt_and_verify(encrypted_or_signed_message)) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
|
331
551
|
end
|
332
552
|
end
|
333
553
|
end
|
@@ -337,13 +557,14 @@ module ActionDispatch
|
|
337
557
|
end
|
338
558
|
|
339
559
|
def call(env)
|
340
|
-
cookie_jar = nil
|
341
560
|
status, headers, body = @app.call(env)
|
342
561
|
|
343
562
|
if cookie_jar = env['action_dispatch.cookies']
|
344
|
-
cookie_jar.
|
345
|
-
|
346
|
-
|
563
|
+
unless cookie_jar.committed?
|
564
|
+
cookie_jar.write(headers)
|
565
|
+
if headers[HTTP_HEADER].respond_to?(:join)
|
566
|
+
headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
|
567
|
+
end
|
347
568
|
end
|
348
569
|
end
|
349
570
|
|