omg-actionpack 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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