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.

Files changed (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +412 -503
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +11 -294
  5. data/lib/abstract_controller/asset_paths.rb +2 -2
  6. data/lib/abstract_controller/base.rb +52 -18
  7. data/lib/abstract_controller/callbacks.rb +87 -89
  8. data/lib/abstract_controller/collector.rb +17 -3
  9. data/lib/abstract_controller/helpers.rb +41 -14
  10. data/lib/abstract_controller/logger.rb +1 -2
  11. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  12. data/lib/abstract_controller/rendering.rb +65 -118
  13. data/lib/abstract_controller/translation.rb +16 -1
  14. data/lib/abstract_controller/url_for.rb +7 -7
  15. data/lib/abstract_controller.rb +2 -10
  16. data/lib/action_controller/base.rb +61 -28
  17. data/lib/action_controller/caching/fragments.rb +30 -54
  18. data/lib/action_controller/caching.rb +38 -35
  19. data/lib/action_controller/log_subscriber.rb +35 -18
  20. data/lib/action_controller/metal/conditional_get.rb +103 -34
  21. data/lib/action_controller/metal/data_streaming.rb +20 -26
  22. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  23. data/lib/action_controller/metal/exceptions.rb +19 -6
  24. data/lib/action_controller/metal/flash.rb +41 -9
  25. data/lib/action_controller/metal/force_ssl.rb +70 -12
  26. data/lib/action_controller/metal/head.rb +30 -7
  27. data/lib/action_controller/metal/helpers.rb +11 -11
  28. data/lib/action_controller/metal/hide_actions.rb +0 -1
  29. data/lib/action_controller/metal/http_authentication.rb +140 -94
  30. data/lib/action_controller/metal/implicit_render.rb +1 -1
  31. data/lib/action_controller/metal/instrumentation.rb +11 -7
  32. data/lib/action_controller/metal/live.rb +328 -0
  33. data/lib/action_controller/metal/mime_responds.rb +161 -152
  34. data/lib/action_controller/metal/params_wrapper.rb +126 -81
  35. data/lib/action_controller/metal/rack_delegation.rb +10 -4
  36. data/lib/action_controller/metal/redirecting.rb +44 -41
  37. data/lib/action_controller/metal/renderers.rb +48 -19
  38. data/lib/action_controller/metal/rendering.rb +46 -11
  39. data/lib/action_controller/metal/request_forgery_protection.rb +250 -29
  40. data/lib/action_controller/metal/streaming.rb +30 -38
  41. data/lib/action_controller/metal/strong_parameters.rb +669 -0
  42. data/lib/action_controller/metal/testing.rb +12 -18
  43. data/lib/action_controller/metal/url_for.rb +31 -29
  44. data/lib/action_controller/metal.rb +31 -40
  45. data/lib/action_controller/model_naming.rb +12 -0
  46. data/lib/action_controller/railtie.rb +38 -18
  47. data/lib/action_controller/railties/helpers.rb +22 -0
  48. data/lib/action_controller/test_case.rb +359 -173
  49. data/lib/action_controller.rb +9 -16
  50. data/lib/action_dispatch/http/cache.rb +64 -11
  51. data/lib/action_dispatch/http/filter_parameters.rb +20 -10
  52. data/lib/action_dispatch/http/filter_redirect.rb +38 -0
  53. data/lib/action_dispatch/http/headers.rb +85 -17
  54. data/lib/action_dispatch/http/mime_negotiation.rb +55 -5
  55. data/lib/action_dispatch/http/mime_type.rb +167 -114
  56. data/lib/action_dispatch/http/mime_types.rb +2 -1
  57. data/lib/action_dispatch/http/parameter_filter.rb +44 -46
  58. data/lib/action_dispatch/http/parameters.rb +30 -46
  59. data/lib/action_dispatch/http/rack_cache.rb +2 -3
  60. data/lib/action_dispatch/http/request.rb +108 -45
  61. data/lib/action_dispatch/http/response.rb +247 -48
  62. data/lib/action_dispatch/http/upload.rb +60 -29
  63. data/lib/action_dispatch/http/url.rb +135 -45
  64. data/lib/action_dispatch/journey/backwards.rb +5 -0
  65. data/lib/action_dispatch/journey/formatter.rb +166 -0
  66. data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
  67. data/lib/action_dispatch/journey/gtg/simulator.rb +47 -0
  68. data/lib/action_dispatch/journey/gtg/transition_table.rb +157 -0
  69. data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
  70. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  71. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  72. data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
  73. data/lib/action_dispatch/journey/nodes/node.rb +128 -0
  74. data/lib/action_dispatch/journey/parser.rb +198 -0
  75. data/lib/action_dispatch/journey/parser.y +49 -0
  76. data/lib/action_dispatch/journey/parser_extras.rb +23 -0
  77. data/lib/action_dispatch/journey/path/pattern.rb +193 -0
  78. data/lib/action_dispatch/journey/route.rb +125 -0
  79. data/lib/action_dispatch/journey/router/strexp.rb +27 -0
  80. data/lib/action_dispatch/journey/router/utils.rb +93 -0
  81. data/lib/action_dispatch/journey/router.rb +144 -0
  82. data/lib/action_dispatch/journey/routes.rb +80 -0
  83. data/lib/action_dispatch/journey/scanner.rb +61 -0
  84. data/lib/action_dispatch/journey/visitors.rb +221 -0
  85. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  86. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  87. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  88. data/lib/action_dispatch/journey.rb +5 -0
  89. data/lib/action_dispatch/middleware/callbacks.rb +16 -11
  90. data/lib/action_dispatch/middleware/cookies.rb +346 -125
  91. data/lib/action_dispatch/middleware/debug_exceptions.rb +52 -24
  92. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -9
  93. data/lib/action_dispatch/middleware/flash.rb +85 -72
  94. data/lib/action_dispatch/middleware/params_parser.rb +16 -31
  95. data/lib/action_dispatch/middleware/public_exceptions.rb +39 -14
  96. data/lib/action_dispatch/middleware/reloader.rb +16 -7
  97. data/lib/action_dispatch/middleware/remote_ip.rb +132 -40
  98. data/lib/action_dispatch/middleware/request_id.rb +3 -7
  99. data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
  100. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  101. data/lib/action_dispatch/middleware/session/cookie_store.rb +84 -29
  102. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
  103. data/lib/action_dispatch/middleware/show_exceptions.rb +15 -44
  104. data/lib/action_dispatch/middleware/ssl.rb +72 -0
  105. data/lib/action_dispatch/middleware/stack.rb +6 -1
  106. data/lib/action_dispatch/middleware/static.rb +80 -23
  107. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +34 -0
  108. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  109. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +27 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +133 -5
  115. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  123. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  125. data/lib/action_dispatch/railtie.rb +19 -6
  126. data/lib/action_dispatch/request/session.rb +193 -0
  127. data/lib/action_dispatch/request/utils.rb +35 -0
  128. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  129. data/lib/action_dispatch/routing/inspector.rb +234 -0
  130. data/lib/action_dispatch/routing/mapper.rb +897 -436
  131. data/lib/action_dispatch/routing/polymorphic_routes.rb +213 -92
  132. data/lib/action_dispatch/routing/redirection.rb +97 -37
  133. data/lib/action_dispatch/routing/route_set.rb +432 -239
  134. data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
  135. data/lib/action_dispatch/routing/url_for.rb +63 -34
  136. data/lib/action_dispatch/routing.rb +57 -89
  137. data/lib/action_dispatch/testing/assertions/dom.rb +2 -36
  138. data/lib/action_dispatch/testing/assertions/response.rb +24 -38
  139. data/lib/action_dispatch/testing/assertions/routing.rb +55 -54
  140. data/lib/action_dispatch/testing/assertions/selector.rb +2 -434
  141. data/lib/action_dispatch/testing/assertions/tag.rb +2 -137
  142. data/lib/action_dispatch/testing/assertions.rb +11 -7
  143. data/lib/action_dispatch/testing/integration.rb +88 -72
  144. data/lib/action_dispatch/testing/test_process.rb +9 -6
  145. data/lib/action_dispatch/testing/test_request.rb +13 -9
  146. data/lib/action_dispatch/testing/test_response.rb +1 -5
  147. data/lib/action_dispatch.rb +24 -21
  148. data/lib/action_pack/gem_version.rb +15 -0
  149. data/lib/action_pack/version.rb +5 -7
  150. data/lib/action_pack.rb +1 -1
  151. metadata +181 -292
  152. data/lib/abstract_controller/layouts.rb +0 -423
  153. data/lib/abstract_controller/view_paths.rb +0 -96
  154. data/lib/action_controller/caching/actions.rb +0 -185
  155. data/lib/action_controller/caching/pages.rb +0 -187
  156. data/lib/action_controller/caching/sweeping.rb +0 -97
  157. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  158. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  159. data/lib/action_controller/deprecated.rb +0 -3
  160. data/lib/action_controller/metal/compatibility.rb +0 -65
  161. data/lib/action_controller/metal/responder.rb +0 -286
  162. data/lib/action_controller/metal/session_management.rb +0 -14
  163. data/lib/action_controller/railties/paths.rb +0 -25
  164. data/lib/action_controller/record_identifier.rb +0 -85
  165. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  166. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  167. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  168. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  169. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  170. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  171. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  172. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  173. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  174. data/lib/action_dispatch/middleware/head.rb +0 -18
  175. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  176. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  177. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  178. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  179. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  180. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  181. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  182. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  183. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  184. data/lib/action_view/asset_paths.rb +0 -142
  185. data/lib/action_view/base.rb +0 -220
  186. data/lib/action_view/buffers.rb +0 -43
  187. data/lib/action_view/context.rb +0 -36
  188. data/lib/action_view/flows.rb +0 -79
  189. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  190. data/lib/action_view/helpers/asset_paths.rb +0 -7
  191. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  192. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  193. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  194. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  195. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  196. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  197. data/lib/action_view/helpers/cache_helper.rb +0 -64
  198. data/lib/action_view/helpers/capture_helper.rb +0 -203
  199. data/lib/action_view/helpers/controller_helper.rb +0 -25
  200. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  201. data/lib/action_view/helpers/date_helper.rb +0 -1062
  202. data/lib/action_view/helpers/debug_helper.rb +0 -40
  203. data/lib/action_view/helpers/form_helper.rb +0 -1486
  204. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  205. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  206. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  207. data/lib/action_view/helpers/number_helper.rb +0 -622
  208. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  209. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  210. data/lib/action_view/helpers/rendering_helper.rb +0 -90
  211. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  212. data/lib/action_view/helpers/tag_helper.rb +0 -160
  213. data/lib/action_view/helpers/text_helper.rb +0 -426
  214. data/lib/action_view/helpers/translation_helper.rb +0 -91
  215. data/lib/action_view/helpers/url_helper.rb +0 -693
  216. data/lib/action_view/helpers.rb +0 -60
  217. data/lib/action_view/locale/en.yml +0 -160
  218. data/lib/action_view/log_subscriber.rb +0 -28
  219. data/lib/action_view/lookup_context.rb +0 -254
  220. data/lib/action_view/path_set.rb +0 -89
  221. data/lib/action_view/railtie.rb +0 -55
  222. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  223. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  224. data/lib/action_view/renderer/renderer.rb +0 -54
  225. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  226. data/lib/action_view/renderer/template_renderer.rb +0 -94
  227. data/lib/action_view/template/error.rb +0 -128
  228. data/lib/action_view/template/handlers/builder.rb +0 -26
  229. data/lib/action_view/template/handlers/erb.rb +0 -125
  230. data/lib/action_view/template/handlers.rb +0 -50
  231. data/lib/action_view/template/resolver.rb +0 -272
  232. data/lib/action_view/template/text.rb +0 -30
  233. data/lib/action_view/template.rb +0 -337
  234. data/lib/action_view/test_case.rb +0 -245
  235. data/lib/action_view/testing/resolvers.rb +0 -50
  236. data/lib/action_view.rb +0 -84
  237. data/lib/sprockets/assets.rake +0 -99
  238. data/lib/sprockets/bootstrap.rb +0 -37
  239. data/lib/sprockets/compressors.rb +0 -83
  240. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  241. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  242. data/lib/sprockets/helpers.rb +0 -6
  243. data/lib/sprockets/railtie.rb +0 -62
  244. 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 for writing:
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
- # # Assign an array of values to a cookie.
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] = { :value => "XJ-122", :expires => 1.hour.from_now }
31
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
29
32
  #
30
- # # Sets a signed cookie, which prevents a user from tampering with its value.
31
- # # The cookie is signed by your app's <tt>config.secret_token</tt> value.
32
- # # Rails generates this value by default when you create a new Rails app.
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 for reading:
44
+ # Examples of reading:
42
45
  #
43
- # cookies[:user_name] # => "david"
44
- # cookies.size # => 2
45
- # cookies[:lat_lon] # => [47.68, -122.37]
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[:key] = {
54
- # :value => 'a yummy cookie',
55
- # :expires => 1.year.from_now,
56
- # :domain => 'domain.com'
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(:key, :domain => 'domain.com')
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 or list of values (as an array).
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 keys.
74
+ # <tt>:all</tt> or <tt>Array</tt> again when deleting cookies.
71
75
  #
72
- # :domain => nil # Does not sets cookie domain. (default)
73
- # :domain => :all # Allow the cookie for the top most level
74
- # domain and subdomains.
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 a only transmitted to HTTPS servers.
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 = "Set-Cookie".freeze
83
- TOKEN_KEY = "action_dispatch.secret_token".freeze
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
- class CookieOverflow < StandardError; end
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
- secret = request.env[TOKEN_KEY]
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(secret, host, secure).tap do |hash|
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(secret = nil, host = nil, secure = false)
116
- @secret = secret
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
- @closed = false
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[/^\.?(.*)$/, 1] }
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 very cookie
163
- # value, or a hash of options as documented above.
164
- def []=(key, options)
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[key.to_s] != value or options[:expires]
176
- @cookies[key.to_s] = value
177
- @set_cookies[key.to_s] = options
178
- @delete_cookies.delete(key.to_s)
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 setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
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(key, options = {})
188
- options.symbolize_keys!
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(key.to_s)
193
- @delete_cookies[key.to_s] = options
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.clear
241
- @delete_cookies.clear
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 < CookieJar #:nodoc:
255
- def initialize(parent_jar, secret)
256
- @parent_jar, @secret = parent_jar, secret
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 []=(key, options)
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[key] = options
391
+ @parent_jar[name] = options
268
392
  end
393
+ end
269
394
 
270
- def signed
271
- @signed ||= SignedCookieJar.new(self, @secret)
395
+ class JsonSerializer # :nodoc:
396
+ def self.load(value)
397
+ ActiveSupport::JSON.decode(value)
272
398
  end
273
399
 
274
- def method_missing(method, *arguments, &block)
275
- @parent_jar.send(method, *arguments, &block)
400
+ def self.dump(value)
401
+ ActiveSupport::JSON.encode(value)
276
402
  end
277
403
  end
278
404
 
279
- class SignedCookieJar < CookieJar #:nodoc:
280
- MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
281
- SECRET_MIN_LENGTH = 30 # Characters
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, secret)
284
- ensure_secret_secure(secret)
450
+ def initialize(parent_jar, key_generator, options = {})
285
451
  @parent_jar = parent_jar
286
- @verifier = ActiveSupport::MessageVerifier.new(secret)
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
- @verifier.verify(signed_message)
459
+ deserialize name, verify(signed_message)
292
460
  end
293
- rescue ActiveSupport::MessageVerifier::InvalidSignature
294
- nil
295
461
  end
296
462
 
297
- def []=(key, options)
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
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
306
- @parent_jar[key] = options
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 method_missing(method, *arguments, &block)
310
- @parent_jar.send(method, *arguments, &block)
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
- protected
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
- # To prevent users from using something insecure like "Password" we make sure that the
316
- # secret they've provided is at least 30 characters in length.
317
- def ensure_secret_secure(secret)
318
- if secret.blank?
319
- raise ArgumentError, "A secret is required to generate an " +
320
- "integrity hash for cookie session data. Use " +
321
- "config.secret_token = \"some secret phrase of at " +
322
- "least #{SECRET_MIN_LENGTH} characters\"" +
323
- "in config/initializers/secret_token.rb"
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
- if secret.length < SECRET_MIN_LENGTH
327
- raise ArgumentError, "Secret should be something secure, " +
328
- "like \"#{SecureRandom.hex(16)}\". The value you " +
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.write(headers)
345
- if headers[HTTP_HEADER].respond_to?(:join)
346
- headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
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