omg-actionpack 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +691 -0
  65. data/lib/action_controller.rb +80 -0
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,719 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support/core_ext/hash/keys"
6
+ require "active_support/key_generator"
7
+ require "active_support/message_verifier"
8
+ require "active_support/json"
9
+ require "rack/utils"
10
+
11
+ module ActionDispatch
12
+ module RequestCookieMethods
13
+ def cookie_jar
14
+ fetch_header("action_dispatch.cookies") do
15
+ self.cookie_jar = Cookies::CookieJar.build(self, cookies)
16
+ end
17
+ end
18
+
19
+ # :stopdoc:
20
+ prepend Module.new {
21
+ def commit_cookie_jar!
22
+ cookie_jar.commit!
23
+ end
24
+ }
25
+
26
+ def have_cookie_jar?
27
+ has_header? "action_dispatch.cookies"
28
+ end
29
+
30
+ def cookie_jar=(jar)
31
+ set_header "action_dispatch.cookies", jar
32
+ end
33
+
34
+ def key_generator
35
+ get_header Cookies::GENERATOR_KEY
36
+ end
37
+
38
+ def signed_cookie_salt
39
+ get_header Cookies::SIGNED_COOKIE_SALT
40
+ end
41
+
42
+ def encrypted_cookie_salt
43
+ get_header Cookies::ENCRYPTED_COOKIE_SALT
44
+ end
45
+
46
+ def encrypted_signed_cookie_salt
47
+ get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT
48
+ end
49
+
50
+ def authenticated_encrypted_cookie_salt
51
+ get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT
52
+ end
53
+
54
+ def use_authenticated_cookie_encryption
55
+ get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION
56
+ end
57
+
58
+ def encrypted_cookie_cipher
59
+ get_header Cookies::ENCRYPTED_COOKIE_CIPHER
60
+ end
61
+
62
+ def signed_cookie_digest
63
+ get_header Cookies::SIGNED_COOKIE_DIGEST
64
+ end
65
+
66
+ def secret_key_base
67
+ get_header Cookies::SECRET_KEY_BASE
68
+ end
69
+
70
+ def cookies_serializer
71
+ get_header Cookies::COOKIES_SERIALIZER
72
+ end
73
+
74
+ def cookies_same_site_protection
75
+ get_header(Cookies::COOKIES_SAME_SITE_PROTECTION)&.call(self)
76
+ end
77
+
78
+ def cookies_digest
79
+ get_header Cookies::COOKIES_DIGEST
80
+ end
81
+
82
+ def cookies_rotations
83
+ get_header Cookies::COOKIES_ROTATIONS
84
+ end
85
+
86
+ def use_cookies_with_metadata
87
+ get_header Cookies::USE_COOKIES_WITH_METADATA
88
+ end
89
+
90
+ # :startdoc:
91
+ end
92
+
93
+ ActiveSupport.on_load(:action_dispatch_request) do
94
+ include RequestCookieMethods
95
+ end
96
+
97
+ # Read and write data to cookies through ActionController::Cookies#cookies.
98
+ #
99
+ # When reading cookie data, the data is read from the HTTP request header,
100
+ # Cookie. When writing cookie data, the data is sent out in the HTTP response
101
+ # header, `Set-Cookie`.
102
+ #
103
+ # Examples of writing:
104
+ #
105
+ # # Sets a simple session cookie.
106
+ # # This cookie will be deleted when the user's browser is closed.
107
+ # cookies[:user_name] = "david"
108
+ #
109
+ # # Cookie values are String-based. Other data types need to be serialized.
110
+ # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
111
+ #
112
+ # # Sets a cookie that expires in 1 hour.
113
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour }
114
+ #
115
+ # # Sets a cookie that expires at a specific time.
116
+ # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
117
+ #
118
+ # # Sets a signed cookie, which prevents users from tampering with its value.
119
+ # cookies.signed[:user_id] = current_user.id
120
+ # # It can be read using the signed method.
121
+ # cookies.signed[:user_id] # => 123
122
+ #
123
+ # # Sets an encrypted cookie value before sending it to the client which
124
+ # # prevent users from reading and tampering with its value.
125
+ # cookies.encrypted[:discount] = 45
126
+ # # It can be read using the encrypted method.
127
+ # cookies.encrypted[:discount] # => 45
128
+ #
129
+ # # Sets a "permanent" cookie (which expires in 20 years from now).
130
+ # cookies.permanent[:login] = "XJ-122"
131
+ #
132
+ # # You can also chain these methods:
133
+ # cookies.signed.permanent[:login] = "XJ-122"
134
+ #
135
+ # Examples of reading:
136
+ #
137
+ # cookies[:user_name] # => "david"
138
+ # cookies.size # => 2
139
+ # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
140
+ # cookies.signed[:login] # => "XJ-122"
141
+ # cookies.encrypted[:discount] # => 45
142
+ #
143
+ # Example for deleting:
144
+ #
145
+ # cookies.delete :user_name
146
+ #
147
+ # Please note that if you specify a `:domain` when setting a cookie, you must
148
+ # also specify the domain when deleting the cookie:
149
+ #
150
+ # cookies[:name] = {
151
+ # value: 'a yummy cookie',
152
+ # expires: 1.year,
153
+ # domain: 'domain.com'
154
+ # }
155
+ #
156
+ # cookies.delete(:name, domain: 'domain.com')
157
+ #
158
+ # The option symbols for setting cookies are:
159
+ #
160
+ # * `:value` - The cookie's value.
161
+ # * `:path` - The path for which this cookie applies. Defaults to the root of
162
+ # the application.
163
+ # * `:domain` - The domain for which this cookie applies so you can restrict
164
+ # to the domain level. If you use a schema like www.example.com and want to
165
+ # share session with user.example.com set `:domain` to `:all`. To support
166
+ # multiple domains, provide an array, and the first domain matching
167
+ # `request.host` will be used. Make sure to specify the `:domain` option
168
+ # with `:all` or `Array` again when deleting cookies. For more flexibility
169
+ # you can set the domain on a per-request basis by specifying `:domain` with
170
+ # a proc.
171
+ #
172
+ # domain: nil # Does not set cookie domain. (default)
173
+ # domain: :all # Allow the cookie for the top most level
174
+ # # domain and subdomains.
175
+ # domain: %w(.example.com .example.org) # Allow the cookie
176
+ # # for concrete domain names.
177
+ # domain: proc { Tenant.current.cookie_domain } # Set cookie domain dynamically
178
+ # domain: proc { |req| ".sub.#{req.host}" } # Set cookie domain dynamically based on request
179
+ #
180
+ # * `:tld_length` - When using `:domain => :all`, this option can be used to
181
+ # explicitly set the TLD length when using a short (<= 3 character) domain
182
+ # that is being interpreted as part of a TLD. For example, to share cookies
183
+ # between user1.lvh.me and user2.lvh.me, set `:tld_length` to 2.
184
+ # * `:expires` - The time at which this cookie expires, as a Time or
185
+ # ActiveSupport::Duration object.
186
+ # * `:secure` - Whether this cookie is only transmitted to HTTPS servers.
187
+ # Default is `false`.
188
+ # * `:httponly` - Whether this cookie is accessible via scripting or only
189
+ # HTTP. Defaults to `false`.
190
+ # * `:same_site` - The value of the `SameSite` cookie attribute, which
191
+ # determines how this cookie should be restricted in cross-site contexts.
192
+ # Possible values are `nil`, `:none`, `:lax`, and `:strict`. Defaults to
193
+ # `:lax`.
194
+ #
195
+ class Cookies
196
+ HTTP_HEADER = "Set-Cookie"
197
+ GENERATOR_KEY = "action_dispatch.key_generator"
198
+ SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt"
199
+ ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt"
200
+ ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt"
201
+ AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt"
202
+ USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption"
203
+ ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher"
204
+ SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest"
205
+ SECRET_KEY_BASE = "action_dispatch.secret_key_base"
206
+ COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
207
+ COOKIES_DIGEST = "action_dispatch.cookies_digest"
208
+ COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
209
+ COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection"
210
+ USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
211
+
212
+ # Cookies can typically store 4096 bytes.
213
+ MAX_COOKIE_SIZE = 4096
214
+
215
+ # Raised when storing more than 4K of session data.
216
+ CookieOverflow = Class.new StandardError
217
+
218
+ # Include in a cookie jar to allow chaining, e.g. `cookies.permanent.signed`.
219
+ module ChainedCookieJars
220
+ # Returns a jar that'll automatically set the assigned cookies to have an
221
+ # expiration date 20 years from now. Example:
222
+ #
223
+ # cookies.permanent[:prefers_open_id] = true
224
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
225
+ #
226
+ # This jar is only meant for writing. You'll read permanent cookies through the
227
+ # regular accessor.
228
+ #
229
+ # This jar allows chaining with the signed jar as well, so you can set
230
+ # permanent, signed cookies. Examples:
231
+ #
232
+ # cookies.permanent.signed[:remember_me] = current_user.id
233
+ # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
234
+ def permanent
235
+ @permanent ||= PermanentCookieJar.new(self)
236
+ end
237
+
238
+ # Returns a jar that'll automatically generate a signed representation of cookie
239
+ # value and verify it when reading from the cookie again. This is useful for
240
+ # creating cookies with values that the user is not supposed to change. If a
241
+ # signed cookie was tampered with by the user (or a 3rd party), `nil` will be
242
+ # returned.
243
+ #
244
+ # This jar requires that you set a suitable secret for the verification on your
245
+ # app's `secret_key_base`.
246
+ #
247
+ # Example:
248
+ #
249
+ # cookies.signed[:discount] = 45
250
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
251
+ #
252
+ # cookies.signed[:discount] # => 45
253
+ def signed
254
+ @signed ||= SignedKeyRotatingCookieJar.new(self)
255
+ end
256
+
257
+ # Returns a jar that'll automatically encrypt cookie values before sending them
258
+ # to the client and will decrypt them for read. If the cookie was tampered with
259
+ # by the user (or a 3rd party), `nil` will be returned.
260
+ #
261
+ # If `config.action_dispatch.encrypted_cookie_salt` and
262
+ # `config.action_dispatch.encrypted_signed_cookie_salt` are both set, legacy
263
+ # cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
264
+ #
265
+ # This jar requires that you set a suitable secret for the verification on your
266
+ # app's `secret_key_base`.
267
+ #
268
+ # Example:
269
+ #
270
+ # cookies.encrypted[:discount] = 45
271
+ # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
272
+ #
273
+ # cookies.encrypted[:discount] # => 45
274
+ def encrypted
275
+ @encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
276
+ end
277
+
278
+ # Returns the `signed` or `encrypted` jar, preferring `encrypted` if
279
+ # `secret_key_base` is set. Used by ActionDispatch::Session::CookieStore to
280
+ # avoid the need to introduce new cookie stores.
281
+ def signed_or_encrypted
282
+ @signed_or_encrypted ||=
283
+ if request.secret_key_base.present?
284
+ encrypted
285
+ else
286
+ signed
287
+ end
288
+ end
289
+
290
+ private
291
+ def upgrade_legacy_hmac_aes_cbc_cookies?
292
+ request.secret_key_base.present? &&
293
+ request.encrypted_signed_cookie_salt.present? &&
294
+ request.encrypted_cookie_salt.present? &&
295
+ request.use_authenticated_cookie_encryption
296
+ end
297
+
298
+ def prepare_upgrade_legacy_hmac_aes_cbc_cookies?
299
+ request.secret_key_base.present? &&
300
+ request.authenticated_encrypted_cookie_salt.present? &&
301
+ !request.use_authenticated_cookie_encryption
302
+ end
303
+
304
+ def encrypted_cookie_cipher
305
+ request.encrypted_cookie_cipher || "aes-256-gcm"
306
+ end
307
+
308
+ def signed_cookie_digest
309
+ request.signed_cookie_digest || "SHA1"
310
+ end
311
+ end
312
+
313
+ class CookieJar # :nodoc:
314
+ include Enumerable, ChainedCookieJars
315
+
316
+ def self.build(req, cookies)
317
+ jar = new(req)
318
+ jar.update(cookies)
319
+ jar
320
+ end
321
+
322
+ attr_reader :request
323
+
324
+ def initialize(request)
325
+ @set_cookies = {}
326
+ @delete_cookies = {}
327
+ @request = request
328
+ @cookies = {}
329
+ @committed = false
330
+ end
331
+
332
+ def committed?; @committed; end
333
+
334
+ def commit!
335
+ @committed = true
336
+ @set_cookies.freeze
337
+ @delete_cookies.freeze
338
+ end
339
+
340
+ def each(&block)
341
+ @cookies.each(&block)
342
+ end
343
+
344
+ # Returns the value of the cookie by `name`, or `nil` if no such cookie exists.
345
+ def [](name)
346
+ @cookies[name.to_s]
347
+ end
348
+
349
+ def fetch(name, *args, &block)
350
+ @cookies.fetch(name.to_s, *args, &block)
351
+ end
352
+
353
+ def key?(name)
354
+ @cookies.key?(name.to_s)
355
+ end
356
+ alias :has_key? :key?
357
+
358
+ # Returns the cookies as Hash.
359
+ alias :to_hash :to_h
360
+
361
+ def update(other_hash)
362
+ @cookies.update other_hash.stringify_keys
363
+ self
364
+ end
365
+
366
+ def update_cookies_from_jar
367
+ request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
368
+ set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) }
369
+
370
+ @cookies.update set_cookies if set_cookies
371
+ end
372
+
373
+ def to_header
374
+ @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
375
+ end
376
+
377
+ # Sets the cookie named `name`. The second argument may be the cookie's value or
378
+ # a hash of options as documented above.
379
+ def []=(name, options)
380
+ if options.is_a?(Hash)
381
+ options.symbolize_keys!
382
+ value = options[:value]
383
+ else
384
+ value = options
385
+ options = { value: value }
386
+ end
387
+
388
+ handle_options(options)
389
+
390
+ if @cookies[name.to_s] != value || options[:expires]
391
+ @cookies[name.to_s] = value
392
+ @set_cookies[name.to_s] = options
393
+ @delete_cookies.delete(name.to_s)
394
+ end
395
+
396
+ value
397
+ end
398
+
399
+ # Removes the cookie on the client machine by setting the value to an empty
400
+ # string and the expiration date in the past. Like `[]=`, you can pass in an
401
+ # options hash to delete cookies with extra data such as a `:path`.
402
+ #
403
+ # Returns the value of the cookie, or `nil` if the cookie does not exist.
404
+ def delete(name, options = {})
405
+ return unless @cookies.has_key? name.to_s
406
+
407
+ options.symbolize_keys!
408
+ handle_options(options)
409
+
410
+ value = @cookies.delete(name.to_s)
411
+ @delete_cookies[name.to_s] = options
412
+ value
413
+ end
414
+
415
+ # Whether the given cookie is to be deleted by this CookieJar. Like `[]=`, you
416
+ # can pass in an options hash to test if a deletion applies to a specific
417
+ # `:path`, `:domain` etc.
418
+ def deleted?(name, options = {})
419
+ options.symbolize_keys!
420
+ handle_options(options)
421
+ @delete_cookies[name.to_s] == options
422
+ end
423
+
424
+ # Removes all cookies on the client machine by calling `delete` for each cookie.
425
+ def clear(options = {})
426
+ @cookies.each_key { |k| delete(k, options) }
427
+ end
428
+
429
+ def write(response)
430
+ @set_cookies.each do |name, value|
431
+ if write_cookie?(value)
432
+ response.set_cookie(name, value)
433
+ end
434
+ end
435
+
436
+ @delete_cookies.each do |name, value|
437
+ response.delete_cookie(name, value)
438
+ end
439
+ end
440
+
441
+ mattr_accessor :always_write_cookie, default: false
442
+
443
+ private
444
+ def escape(string)
445
+ ::Rack::Utils.escape(string)
446
+ end
447
+
448
+ def write_cookie?(cookie)
449
+ request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion")
450
+ end
451
+
452
+ def handle_options(options)
453
+ if options[:expires].respond_to?(:from_now)
454
+ options[:expires] = options[:expires].from_now
455
+ end
456
+
457
+ options[:path] ||= "/"
458
+
459
+ unless options.key?(:same_site)
460
+ options[:same_site] = request.cookies_same_site_protection
461
+ end
462
+
463
+ if options[:domain] == :all || options[:domain] == "all"
464
+ cookie_domain = ""
465
+ dot_splitted_host = request.host.split(".", -1)
466
+
467
+ # Case where request.host is not an IP address or it's an invalid domain (ip
468
+ # confirms to the domain structure we expect so we explicitly check for ip)
469
+ if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1
470
+ options[:domain] = nil
471
+ return
472
+ end
473
+
474
+ # If there is a provided tld length then we use it otherwise default domain.
475
+ if options[:tld_length].present?
476
+ # Case where the tld_length provided is valid
477
+ if dot_splitted_host.length >= options[:tld_length]
478
+ cookie_domain = dot_splitted_host.last(options[:tld_length]).join(".")
479
+ end
480
+ # Case where tld_length is not provided
481
+ else
482
+ # Regular TLDs
483
+ if !(/\.[^.]{2,3}\.[^.]{2}\z/.match?(request.host))
484
+ cookie_domain = dot_splitted_host.last(2).join(".")
485
+ # **.**, ***.** style TLDs like co.uk and com.au
486
+ else
487
+ cookie_domain = dot_splitted_host.last(3).join(".")
488
+ end
489
+ end
490
+
491
+ options[:domain] = if cookie_domain.present?
492
+ cookie_domain
493
+ end
494
+ elsif options[:domain].is_a? Array
495
+ # If host matches one of the supplied domains.
496
+ options[:domain] = options[:domain].find do |domain|
497
+ domain = domain.delete_prefix(".")
498
+ request.host == domain || request.host.end_with?(".#{domain}")
499
+ end
500
+ elsif options[:domain].respond_to?(:call)
501
+ options[:domain] = options[:domain].call(request)
502
+ end
503
+ end
504
+ end
505
+
506
+ class AbstractCookieJar # :nodoc:
507
+ include ChainedCookieJars
508
+
509
+ def initialize(parent_jar)
510
+ @parent_jar = parent_jar
511
+ end
512
+
513
+ def [](name)
514
+ if data = @parent_jar[name.to_s]
515
+ result = parse(name, data, purpose: "cookie.#{name}")
516
+
517
+ if result.nil?
518
+ parse(name, data)
519
+ else
520
+ result
521
+ end
522
+ end
523
+ end
524
+
525
+ def []=(name, options)
526
+ if options.is_a?(Hash)
527
+ options.symbolize_keys!
528
+ else
529
+ options = { value: options }
530
+ end
531
+
532
+ commit(name, options)
533
+ @parent_jar[name] = options
534
+ end
535
+
536
+ protected
537
+ def request; @parent_jar.request; end
538
+
539
+ private
540
+ def expiry_options(options)
541
+ if options[:expires].respond_to?(:from_now)
542
+ { expires_in: options[:expires] }
543
+ else
544
+ { expires_at: options[:expires] }
545
+ end
546
+ end
547
+
548
+ def cookie_metadata(name, options)
549
+ expiry_options(options).tap do |metadata|
550
+ metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata
551
+ end
552
+ end
553
+
554
+ def parse(name, data, purpose: nil); data; end
555
+ def commit(name, options); end
556
+ end
557
+
558
+ class PermanentCookieJar < AbstractCookieJar # :nodoc:
559
+ private
560
+ def commit(name, options)
561
+ options[:expires] = 20.years.from_now
562
+ end
563
+ end
564
+
565
+ module SerializedCookieJars # :nodoc:
566
+ SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
567
+
568
+ protected
569
+ def digest
570
+ request.cookies_digest || "SHA1"
571
+ end
572
+
573
+ private
574
+ def serializer
575
+ @serializer ||=
576
+ case request.cookies_serializer
577
+ when nil
578
+ ActiveSupport::Messages::SerializerWithFallback[:marshal]
579
+ when :hybrid
580
+ ActiveSupport::Messages::SerializerWithFallback[:json_allow_marshal]
581
+ when Symbol
582
+ ActiveSupport::Messages::SerializerWithFallback[request.cookies_serializer]
583
+ else
584
+ request.cookies_serializer
585
+ end
586
+ end
587
+
588
+ def reserialize?(dumped)
589
+ serializer.is_a?(ActiveSupport::Messages::SerializerWithFallback) &&
590
+ serializer != ActiveSupport::Messages::SerializerWithFallback[:marshal] &&
591
+ !serializer.dumped?(dumped)
592
+ end
593
+
594
+ def parse(name, dumped, force_reserialize: false, **)
595
+ if dumped
596
+ begin
597
+ value = serializer.load(dumped)
598
+ rescue StandardError
599
+ return
600
+ end
601
+
602
+ self[name] = { value: value } if force_reserialize || reserialize?(dumped)
603
+
604
+ value
605
+ end
606
+ end
607
+
608
+ def commit(name, options)
609
+ options[:value] = serializer.dump(options[:value])
610
+ end
611
+
612
+ def check_for_overflow!(name, options)
613
+ if options[:value].bytesize > MAX_COOKIE_SIZE
614
+ raise CookieOverflow, "#{name} cookie overflowed with size #{options[:value].bytesize} bytes"
615
+ end
616
+ end
617
+ end
618
+
619
+ class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
620
+ include SerializedCookieJars
621
+
622
+ def initialize(parent_jar)
623
+ super
624
+
625
+ secret = request.key_generator.generate_key(request.signed_cookie_salt)
626
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
627
+
628
+ request.cookies_rotations.signed.each do |(*secrets)|
629
+ options = secrets.extract_options!
630
+ @verifier.rotate(*secrets, serializer: SERIALIZER, **options)
631
+ end
632
+ end
633
+
634
+ private
635
+ def parse(name, signed_message, purpose: nil)
636
+ rotated = false
637
+ data = @verifier.verified(signed_message, purpose: purpose, on_rotation: -> { rotated = true })
638
+ super(name, data, force_reserialize: rotated)
639
+ end
640
+
641
+ def commit(name, options)
642
+ super
643
+ options[:value] = @verifier.generate(options[:value], **cookie_metadata(name, options))
644
+ check_for_overflow!(name, options)
645
+ end
646
+ end
647
+
648
+ class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
649
+ include SerializedCookieJars
650
+
651
+ def initialize(parent_jar)
652
+ super
653
+
654
+ if request.use_authenticated_cookie_encryption
655
+ key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
656
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len)
657
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER)
658
+ else
659
+ key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")
660
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len)
661
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
662
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
663
+ end
664
+
665
+ request.cookies_rotations.encrypted.each do |(*secrets)|
666
+ options = secrets.extract_options!
667
+ @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
668
+ end
669
+
670
+ if upgrade_legacy_hmac_aes_cbc_cookies?
671
+ legacy_cipher = "aes-256-cbc"
672
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher))
673
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
674
+
675
+ @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
676
+ elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies?
677
+ future_cipher = encrypted_cookie_cipher
678
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher))
679
+
680
+ @encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
681
+ end
682
+ end
683
+
684
+ private
685
+ def parse(name, encrypted_message, purpose: nil)
686
+ rotated = false
687
+ data = @encryptor.decrypt_and_verify(encrypted_message, purpose: purpose, on_rotation: -> { rotated = true })
688
+ super(name, data, force_reserialize: rotated)
689
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
690
+ nil
691
+ end
692
+
693
+ def commit(name, options)
694
+ super
695
+ options[:value] = @encryptor.encrypt_and_sign(options[:value], **cookie_metadata(name, options))
696
+ check_for_overflow!(name, options)
697
+ end
698
+ end
699
+
700
+ def initialize(app)
701
+ @app = app
702
+ end
703
+
704
+ def call(env)
705
+ request = ActionDispatch::Request.new(env)
706
+ response = @app.call(env)
707
+
708
+ if request.have_cookie_jar?
709
+ cookie_jar = request.cookie_jar
710
+ unless cookie_jar.committed?
711
+ response = Rack::Response[*response]
712
+ cookie_jar.write(response)
713
+ end
714
+ end
715
+
716
+ response.to_a
717
+ end
718
+ end
719
+ end