actionpack 7.1.5.1 → 8.1.2

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 (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +308 -523
  3. data/README.rdoc +1 -1
  4. data/lib/abstract_controller/asset_paths.rb +6 -2
  5. data/lib/abstract_controller/base.rb +104 -105
  6. data/lib/abstract_controller/caching/fragments.rb +50 -53
  7. data/lib/abstract_controller/caching.rb +8 -3
  8. data/lib/abstract_controller/callbacks.rb +70 -62
  9. data/lib/abstract_controller/collector.rb +7 -7
  10. data/lib/abstract_controller/deprecator.rb +2 -0
  11. data/lib/abstract_controller/error.rb +2 -0
  12. data/lib/abstract_controller/helpers.rb +71 -84
  13. data/lib/abstract_controller/logger.rb +4 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/rendering.rb +13 -13
  16. data/lib/abstract_controller/translation.rb +12 -13
  17. data/lib/abstract_controller/url_for.rb +8 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api/api_rendering.rb +2 -0
  20. data/lib/action_controller/api.rb +76 -72
  21. data/lib/action_controller/base.rb +199 -126
  22. data/lib/action_controller/caching.rb +16 -14
  23. data/lib/action_controller/deprecator.rb +2 -0
  24. data/lib/action_controller/form_builder.rb +21 -18
  25. data/lib/action_controller/log_subscriber.rb +23 -2
  26. data/lib/action_controller/metal/allow_browser.rb +133 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  28. data/lib/action_controller/metal/conditional_get.rb +217 -175
  29. data/lib/action_controller/metal/content_security_policy.rb +25 -24
  30. data/lib/action_controller/metal/cookies.rb +4 -2
  31. data/lib/action_controller/metal/data_streaming.rb +72 -63
  32. data/lib/action_controller/metal/default_headers.rb +5 -3
  33. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  34. data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
  35. data/lib/action_controller/metal/exceptions.rb +16 -9
  36. data/lib/action_controller/metal/flash.rb +13 -14
  37. data/lib/action_controller/metal/head.rb +15 -11
  38. data/lib/action_controller/metal/helpers.rb +63 -55
  39. data/lib/action_controller/metal/http_authentication.rb +209 -201
  40. data/lib/action_controller/metal/implicit_render.rb +17 -15
  41. data/lib/action_controller/metal/instrumentation.rb +16 -14
  42. data/lib/action_controller/metal/live.rb +177 -128
  43. data/lib/action_controller/metal/logging.rb +6 -4
  44. data/lib/action_controller/metal/mime_responds.rb +151 -142
  45. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  46. data/lib/action_controller/metal/params_wrapper.rb +57 -59
  47. data/lib/action_controller/metal/permissions_policy.rb +22 -12
  48. data/lib/action_controller/metal/rate_limiting.rb +92 -0
  49. data/lib/action_controller/metal/redirecting.rb +213 -94
  50. data/lib/action_controller/metal/renderers.rb +78 -57
  51. data/lib/action_controller/metal/rendering.rb +111 -77
  52. data/lib/action_controller/metal/request_forgery_protection.rb +182 -143
  53. data/lib/action_controller/metal/rescue.rb +20 -9
  54. data/lib/action_controller/metal/streaming.rb +118 -195
  55. data/lib/action_controller/metal/strong_parameters.rb +720 -530
  56. data/lib/action_controller/metal/testing.rb +2 -0
  57. data/lib/action_controller/metal/url_for.rb +17 -15
  58. data/lib/action_controller/metal.rb +86 -60
  59. data/lib/action_controller/railtie.rb +36 -15
  60. data/lib/action_controller/railties/helpers.rb +2 -0
  61. data/lib/action_controller/renderer.rb +41 -36
  62. data/lib/action_controller/structured_event_subscriber.rb +116 -0
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +160 -131
  65. data/lib/action_controller.rb +5 -1
  66. data/lib/action_dispatch/constants.rb +8 -0
  67. data/lib/action_dispatch/deprecator.rb +2 -0
  68. data/lib/action_dispatch/http/cache.rb +163 -35
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +54 -39
  71. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +22 -22
  74. data/lib/action_dispatch/http/mime_negotiation.rb +89 -41
  75. data/lib/action_dispatch/http/mime_type.rb +25 -21
  76. data/lib/action_dispatch/http/mime_types.rb +3 -0
  77. data/lib/action_dispatch/http/param_builder.rb +187 -0
  78. data/lib/action_dispatch/http/param_error.rb +26 -0
  79. data/lib/action_dispatch/http/parameters.rb +14 -12
  80. data/lib/action_dispatch/http/permissions_policy.rb +25 -36
  81. data/lib/action_dispatch/http/query_parser.rb +55 -0
  82. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  83. data/lib/action_dispatch/http/request.rb +141 -92
  84. data/lib/action_dispatch/http/response.rb +137 -77
  85. data/lib/action_dispatch/http/upload.rb +18 -16
  86. data/lib/action_dispatch/http/url.rb +187 -89
  87. data/lib/action_dispatch/journey/formatter.rb +21 -9
  88. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  89. data/lib/action_dispatch/journey/gtg/simulator.rb +34 -11
  90. data/lib/action_dispatch/journey/gtg/transition_table.rb +47 -53
  91. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  92. data/lib/action_dispatch/journey/nodes/node.rb +8 -6
  93. data/lib/action_dispatch/journey/parser.rb +99 -195
  94. data/lib/action_dispatch/journey/path/pattern.rb +4 -1
  95. data/lib/action_dispatch/journey/route.rb +54 -38
  96. data/lib/action_dispatch/journey/router/utils.rb +22 -27
  97. data/lib/action_dispatch/journey/router.rb +63 -83
  98. data/lib/action_dispatch/journey/routes.rb +11 -2
  99. data/lib/action_dispatch/journey/scanner.rb +46 -42
  100. data/lib/action_dispatch/journey/visitors.rb +57 -23
  101. data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
  102. data/lib/action_dispatch/journey.rb +2 -0
  103. data/lib/action_dispatch/log_subscriber.rb +7 -1
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
  106. data/lib/action_dispatch/middleware/callbacks.rb +3 -1
  107. data/lib/action_dispatch/middleware/cookies.rb +125 -106
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +37 -8
  109. data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
  110. data/lib/action_dispatch/middleware/debug_view.rb +13 -5
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +18 -23
  112. data/lib/action_dispatch/middleware/executor.rb +19 -4
  113. data/lib/action_dispatch/middleware/flash.rb +63 -51
  114. data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +14 -12
  116. data/lib/action_dispatch/middleware/reloader.rb +5 -3
  117. data/lib/action_dispatch/middleware/remote_ip.rb +87 -77
  118. data/lib/action_dispatch/middleware/request_id.rb +16 -10
  119. data/lib/action_dispatch/middleware/server_timing.rb +4 -2
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +30 -8
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +16 -16
  125. data/lib/action_dispatch/middleware/ssl.rb +53 -40
  126. data/lib/action_dispatch/middleware/stack.rb +11 -10
  127. data/lib/action_dispatch/middleware/static.rb +33 -31
  128. data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
  130. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
  141. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
  142. data/lib/action_dispatch/railtie.rb +23 -3
  143. data/lib/action_dispatch/request/session.rb +24 -21
  144. data/lib/action_dispatch/request/utils.rb +11 -3
  145. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  146. data/lib/action_dispatch/routing/inspector.rb +85 -60
  147. data/lib/action_dispatch/routing/mapper.rb +1031 -851
  148. data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
  149. data/lib/action_dispatch/routing/redirection.rb +47 -39
  150. data/lib/action_dispatch/routing/route_set.rb +79 -56
  151. data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
  152. data/lib/action_dispatch/routing/url_for.rb +130 -125
  153. data/lib/action_dispatch/routing.rb +150 -148
  154. data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
  155. data/lib/action_dispatch/system_test_case.rb +91 -81
  156. data/lib/action_dispatch/system_testing/browser.rb +16 -23
  157. data/lib/action_dispatch/system_testing/driver.rb +2 -0
  158. data/lib/action_dispatch/system_testing/server.rb +2 -0
  159. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +34 -23
  160. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  161. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  162. data/lib/action_dispatch/testing/assertions/response.rb +52 -25
  163. data/lib/action_dispatch/testing/assertions/routing.rb +168 -87
  164. data/lib/action_dispatch/testing/assertions.rb +2 -0
  165. data/lib/action_dispatch/testing/integration.rb +233 -223
  166. data/lib/action_dispatch/testing/request_encoder.rb +11 -9
  167. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  168. data/lib/action_dispatch/testing/test_process.rb +11 -8
  169. data/lib/action_dispatch/testing/test_request.rb +3 -1
  170. data/lib/action_dispatch/testing/test_response.rb +27 -26
  171. data/lib/action_dispatch.rb +36 -32
  172. data/lib/action_pack/gem_version.rb +6 -4
  173. data/lib/action_pack/version.rb +3 -1
  174. data/lib/action_pack.rb +17 -16
  175. metadata +36 -32
  176. data/lib/action_dispatch/journey/parser.y +0 -50
  177. data/lib/action_dispatch/journey/parser_extras.rb +0 -31
@@ -105,12 +105,10 @@ function match(input) {
105
105
  }
106
106
 
107
107
  if(stdparam_states[state] && default_re.test(token)) {
108
- for(var key in stdparam_states[state]) {
109
- var new_state = stdparam_states[state][key];
110
- highlight_edge(state, new_state);
111
- highlight_state(new_state);
112
- new_states.push([new_state, null]);
113
- }
108
+ var new_state = stdparam_states[state];
109
+ highlight_edge(state, new_state);
110
+ highlight_state(new_state);
111
+ new_states.push([new_state, null]);
114
112
  }
115
113
  }
116
114
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch/journey/router"
4
6
  require "action_dispatch/journey/gtg/builder"
5
7
  require "action_dispatch/journey/gtg/simulator"
@@ -1,12 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionDispatch
4
- class LogSubscriber < ActiveSupport::LogSubscriber
4
+ class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
5
+ class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
6
+
5
7
  def redirect(event)
6
8
  payload = event.payload
7
9
 
8
10
  info { "Redirected to #{payload[:location]}" }
9
11
 
12
+ if ActionDispatch.verbose_redirect_logs
13
+ info { "↳ #{payload[:source_location]}" }
14
+ end
15
+
10
16
  info do
11
17
  status = payload[:status]
12
18
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "uri"
4
6
  require "active_support/actionable_error"
5
7
 
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
- # = Action Dispatch \AssumeSSL
6
+ # # Action Dispatch AssumeSSL
5
7
  #
6
- # When proxying through a load balancer that terminates SSL, the forwarded request will appear
7
- # as though it's HTTP instead of HTTPS to the application. This makes redirects and cookie
8
- # security target HTTP instead of HTTPS. This middleware makes the server assume that the
9
- # proxy already terminated SSL, and that the request really is HTTPS.
8
+ # When proxying through a load balancer that terminates SSL, the forwarded
9
+ # request will appear as though it's HTTP instead of HTTPS to the application.
10
+ # This makes redirects and cookie security target HTTP instead of HTTPS. This
11
+ # middleware makes the server assume that the proxy already terminated SSL, and
12
+ # that the request really is HTTPS.
10
13
  class AssumeSSL
11
14
  def initialize(app)
12
15
  @app = app
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
- # = Action Dispatch \Callbacks
6
+ # # Action Dispatch Callbacks
5
7
  #
6
8
  # Provides callbacks to be executed before and after dispatching the request.
7
9
  class Callbacks
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/hash/keys"
4
6
  require "active_support/key_generator"
5
7
  require "active_support/message_verifier"
@@ -94,97 +96,102 @@ module ActionDispatch
94
96
 
95
97
  # Read and write data to cookies through ActionController::Cookies#cookies.
96
98
  #
97
- # When reading cookie data, the data is read from the HTTP request header, Cookie.
98
- # When writing cookie data, the data is sent out in the HTTP response header, +Set-Cookie+.
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`.
99
102
  #
100
103
  # Examples of writing:
101
104
  #
102
- # # Sets a simple session cookie.
103
- # # This cookie will be deleted when the user's browser is closed.
104
- # cookies[:user_name] = "david"
105
+ # # Sets a simple session cookie.
106
+ # # This cookie will be deleted when the user's browser is closed.
107
+ # cookies[:user_name] = "david"
105
108
  #
106
- # # Cookie values are String-based. Other data types need to be serialized.
107
- # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
109
+ # # Cookie values are String-based. Other data types need to be serialized.
110
+ # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
108
111
  #
109
- # # Sets a cookie that expires in 1 hour.
110
- # cookies[:login] = { value: "XJ-122", expires: 1.hour }
112
+ # # Sets a cookie that expires in 1 hour.
113
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour }
111
114
  #
112
- # # Sets a cookie that expires at a specific time.
113
- # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
115
+ # # Sets a cookie that expires at a specific time.
116
+ # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
114
117
  #
115
- # # Sets a signed cookie, which prevents users from tampering with its value.
116
- # # It can be read using the signed method `cookies.signed[:name]`
117
- # cookies.signed[:user_id] = current_user.id
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
118
122
  #
119
- # # Sets an encrypted cookie value before sending it to the client which
120
- # # prevent users from reading and tampering with its value.
121
- # # It can be read using the encrypted method `cookies.encrypted[:name]`
122
- # cookies.encrypted[:discount] = 45
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
123
128
  #
124
- # # Sets a "permanent" cookie (which expires in 20 years from now).
125
- # cookies.permanent[:login] = "XJ-122"
129
+ # # Sets a "permanent" cookie (which expires in 20 years from now).
130
+ # cookies.permanent[:login] = "XJ-122"
126
131
  #
127
- # # You can also chain these methods:
128
- # cookies.signed.permanent[:login] = "XJ-122"
132
+ # # You can also chain these methods:
133
+ # cookies.signed.permanent[:login] = "XJ-122"
129
134
  #
130
135
  # Examples of reading:
131
136
  #
132
- # cookies[:user_name] # => "david"
133
- # cookies.size # => 2
134
- # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
135
- # cookies.signed[:login] # => "XJ-122"
136
- # cookies.encrypted[:discount] # => 45
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
137
142
  #
138
143
  # Example for deleting:
139
144
  #
140
- # cookies.delete :user_name
145
+ # cookies.delete :user_name
141
146
  #
142
- # Please note that if you specify a +:domain+ when setting a cookie, you must also specify the domain when deleting the cookie:
147
+ # Please note that if you specify a `:domain` when setting a cookie, you must
148
+ # also specify the domain when deleting the cookie:
143
149
  #
144
- # cookies[:name] = {
145
- # value: 'a yummy cookie',
146
- # expires: 1.year,
147
- # domain: 'domain.com'
148
- # }
150
+ # cookies[:name] = {
151
+ # value: 'a yummy cookie',
152
+ # expires: 1.year,
153
+ # domain: 'domain.com'
154
+ # }
149
155
  #
150
- # cookies.delete(:name, domain: 'domain.com')
156
+ # cookies.delete(:name, domain: 'domain.com')
151
157
  #
152
158
  # The option symbols for setting cookies are:
153
159
  #
154
- # * <tt>:value</tt> - The cookie's value.
155
- # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
156
- # of the application.
157
- # * <tt>:domain</tt> - The domain for which this cookie applies so you can
158
- # restrict to the domain level. If you use a schema like www.example.com
159
- # and want to share session with user.example.com set <tt>:domain</tt>
160
- # to <tt>:all</tt>. To support multiple domains, provide an array, and
161
- # the first domain matching <tt>request.host</tt> will be used. Make
162
- # sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
163
- # <tt>Array</tt> again when deleting cookies. For more flexibility you
164
- # can set the domain on a per-request basis by specifying <tt>:domain</tt>
165
- # with a proc.
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.
166
171
  #
167
- # domain: nil # Does not set cookie domain. (default)
168
- # domain: :all # Allow the cookie for the top most level
169
- # # domain and subdomains.
170
- # domain: %w(.example.com .example.org) # Allow the cookie
171
- # # for concrete domain names.
172
- # domain: proc { Tenant.current.cookie_domain } # Set cookie domain dynamically
173
- # domain: proc { |req| ".sub.#{req.host}" } # Set cookie domain dynamically based on request
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
174
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`.
175
194
  #
176
- # * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
177
- # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
178
- # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 2.
179
- # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object.
180
- # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
181
- # Default is +false+.
182
- # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
183
- # only HTTP. Defaults to +false+.
184
- # * <tt>:same_site</tt> - The value of the +SameSite+ cookie attribute, which
185
- # determines how this cookie should be restricted in cross-site contexts.
186
- # Possible values are +nil+, +:none+, +:lax+, and +:strict+. Defaults to
187
- # +:lax+.
188
195
  class Cookies
189
196
  HTTP_HEADER = "Set-Cookie"
190
197
  GENERATOR_KEY = "action_dispatch.key_generator"
@@ -208,59 +215,69 @@ module ActionDispatch
208
215
  # Raised when storing more than 4K of session data.
209
216
  CookieOverflow = Class.new StandardError
210
217
 
211
- # Include in a cookie jar to allow chaining, e.g. +cookies.permanent.signed+.
218
+ # Include in a cookie jar to allow chaining, e.g. `cookies.permanent.signed`.
212
219
  module ChainedCookieJars
213
- # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
220
+ # Returns a jar that'll automatically set the assigned cookies to have an
221
+ # expiration date 20 years from now. Example:
214
222
  #
215
- # cookies.permanent[:prefers_open_id] = true
216
- # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
223
+ # cookies.permanent[:prefers_open_id] = true
224
+ # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
217
225
  #
218
- # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
226
+ # This jar is only meant for writing. You'll read permanent cookies through the
227
+ # regular accessor.
219
228
  #
220
- # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
229
+ # This jar allows chaining with the signed jar as well, so you can set
230
+ # permanent, signed cookies. Examples:
221
231
  #
222
- # cookies.permanent.signed[:remember_me] = current_user.id
223
- # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
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
224
234
  def permanent
225
235
  @permanent ||= PermanentCookieJar.new(self)
226
236
  end
227
237
 
228
- # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
229
- # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
230
- # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
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.
231
243
  #
232
- # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
244
+ # This jar requires that you set a suitable secret for the verification on your
245
+ # app's `secret_key_base`.
233
246
  #
234
247
  # Example:
235
248
  #
236
- # cookies.signed[:discount] = 45
237
- # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
249
+ # cookies.signed[:discount] = 45
250
+ # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
238
251
  #
239
- # cookies.signed[:discount] # => 45
252
+ # cookies.signed[:discount] # => 45
240
253
  def signed
241
254
  @signed ||= SignedKeyRotatingCookieJar.new(self)
242
255
  end
243
256
 
244
- # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
245
- # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
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.
246
260
  #
247
- # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
248
- # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
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.
249
264
  #
250
- # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
265
+ # This jar requires that you set a suitable secret for the verification on your
266
+ # app's `secret_key_base`.
251
267
  #
252
268
  # Example:
253
269
  #
254
- # cookies.encrypted[:discount] = 45
255
- # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
270
+ # cookies.encrypted[:discount] = 45
271
+ # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
256
272
  #
257
- # cookies.encrypted[:discount] # => 45
273
+ # cookies.encrypted[:discount] # => 45
258
274
  def encrypted
259
275
  @encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
260
276
  end
261
277
 
262
- # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
263
- # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
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.
264
281
  def signed_or_encrypted
265
282
  @signed_or_encrypted ||=
266
283
  if request.secret_key_base.present?
@@ -324,7 +341,7 @@ module ActionDispatch
324
341
  @cookies.each(&block)
325
342
  end
326
343
 
327
- # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
344
+ # Returns the value of the cookie by `name`, or `nil` if no such cookie exists.
328
345
  def [](name)
329
346
  @cookies[name.to_s]
330
347
  end
@@ -357,8 +374,8 @@ module ActionDispatch
357
374
  @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
358
375
  end
359
376
 
360
- # Sets the cookie named +name+. The second argument may be the cookie's
361
- # value or a hash of options as documented above.
377
+ # Sets the cookie named `name`. The second argument may be the cookie's value or
378
+ # a hash of options as documented above.
362
379
  def []=(name, options)
363
380
  if options.is_a?(Hash)
364
381
  options.symbolize_keys!
@@ -379,11 +396,11 @@ module ActionDispatch
379
396
  value
380
397
  end
381
398
 
382
- # Removes the cookie on the client machine by setting the value to an empty string
383
- # and the expiration date in the past. Like <tt>[]=</tt>, you can pass in
384
- # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
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`.
385
402
  #
386
- # Returns the value of the cookie, or +nil+ if the cookie does not exist.
403
+ # Returns the value of the cookie, or `nil` if the cookie does not exist.
387
404
  def delete(name, options = {})
388
405
  return unless @cookies.has_key? name.to_s
389
406
 
@@ -395,16 +412,16 @@ module ActionDispatch
395
412
  value
396
413
  end
397
414
 
398
- # Whether the given cookie is to be deleted by this CookieJar.
399
- # Like <tt>[]=</tt>, you can pass in an options hash to test if a
400
- # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
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.
401
418
  def deleted?(name, options = {})
402
419
  options.symbolize_keys!
403
420
  handle_options(options)
404
421
  @delete_cookies[name.to_s] == options
405
422
  end
406
423
 
407
- # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie.
424
+ # Removes all cookies on the client machine by calling `delete` for each cookie.
408
425
  def clear(options = {})
409
426
  @cookies.each_key { |k| delete(k, options) }
410
427
  end
@@ -447,8 +464,8 @@ module ActionDispatch
447
464
  cookie_domain = ""
448
465
  dot_splitted_host = request.host.split(".", -1)
449
466
 
450
- # Case where request.host is not an IP address or it's an invalid domain
451
- # (ip confirms to the domain structure we expect so we explicitly check for ip)
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)
452
469
  if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1
453
470
  options[:domain] = nil
454
471
  return
@@ -593,8 +610,10 @@ module ActionDispatch
593
610
  end
594
611
 
595
612
  def check_for_overflow!(name, options)
596
- if options[:value].bytesize > MAX_COOKIE_SIZE
597
- raise CookieOverflow, "#{name} cookie overflowed with size #{options[:value].bytesize} bytes"
613
+ total_size = name.to_s.bytesize + options[:value].bytesize
614
+
615
+ if total_size > MAX_COOKIE_SIZE
616
+ raise CookieOverflow, "#{name} cookie overflowed with size #{total_size} bytes"
598
617
  end
599
618
  end
600
619
  end
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch/middleware/exception_wrapper"
4
6
  require "action_dispatch/routing/inspector"
5
7
 
6
8
  require "action_view"
7
9
 
8
10
  module ActionDispatch
9
- # = Action Dispatch \DebugExceptions
11
+ # # Action Dispatch DebugExceptions
10
12
  #
11
- # This middleware is responsible for logging exceptions and
12
- # showing a debugging page in case the request is local.
13
+ # This middleware is responsible for logging exceptions and showing a debugging
14
+ # page in case the request is local.
13
15
  class DebugExceptions
14
16
  cattr_reader :interceptors, instance_accessor: false, default: []
15
17
 
@@ -63,7 +65,9 @@ module ActionDispatch
63
65
  content_type = Mime[:text]
64
66
  end
65
67
 
66
- if api_request?(content_type)
68
+ if request.head?
69
+ render(wrapper.status_code, "", content_type)
70
+ elsif api_request?(content_type)
67
71
  render_for_api_request(content_type, wrapper)
68
72
  else
69
73
  render_for_browser_request(request, wrapper)
@@ -115,14 +119,15 @@ module ActionDispatch
115
119
  DebugView.new(
116
120
  request: request,
117
121
  exception_wrapper: wrapper,
118
- # Everything should use the wrapper, but we need to pass
119
- # `exception` for legacy code.
122
+ # Everything should use the wrapper, but we need to pass `exception` for legacy
123
+ # code.
120
124
  exception: wrapper.exception,
121
125
  traces: wrapper.traces,
122
126
  show_source_idx: wrapper.source_to_show_id,
123
127
  trace_to_show: wrapper.trace_to_show,
124
128
  routes_inspector: routes_inspector(wrapper),
125
129
  source_extracts: wrapper.source_extracts,
130
+ exception_message_for_copy: compose_exception_message(wrapper).join("\n"),
126
131
  )
127
132
  end
128
133
 
@@ -136,16 +141,40 @@ module ActionDispatch
136
141
  return unless logger
137
142
  return if !log_rescued_responses?(request) && wrapper.rescue_response?
138
143
 
144
+ message = compose_exception_message(wrapper)
145
+ log_array(logger, message, request)
146
+ end
147
+
148
+ def compose_exception_message(wrapper)
139
149
  trace = wrapper.exception_trace
140
150
 
141
151
  message = []
142
152
  message << " "
143
- message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
153
+ if wrapper.has_cause?
154
+ message << "#{wrapper.exception_class_name} (#{wrapper.message})"
155
+ wrapper.wrapped_causes.each do |wrapped_cause|
156
+ message << "Caused by: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message})"
157
+ end
158
+
159
+ message << "\nInformation for: #{wrapper.exception_class_name} (#{wrapper.message}):"
160
+ else
161
+ message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
162
+ end
163
+
144
164
  message.concat(wrapper.annotated_source_code)
145
165
  message << " "
146
166
  message.concat(trace)
147
167
 
148
- log_array(logger, message, request)
168
+ if wrapper.has_cause?
169
+ wrapper.wrapped_causes.each do |wrapped_cause|
170
+ message << "\nInformation for cause: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message}):"
171
+ message.concat(wrapped_cause.annotated_source_code)
172
+ message << " "
173
+ message.concat(wrapped_cause.exception_trace)
174
+ end
175
+ end
176
+
177
+ message
149
178
  end
150
179
 
151
180
  def log_array(logger, lines, request)
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
- # = Action Dispatch \DebugLocks
6
+ # # Action Dispatch DebugLocks
5
7
  #
6
8
  # This middleware can be used to diagnose deadlocks in the autoload interlock.
7
9
  #
8
10
  # To use it, insert it near the top of the middleware stack, using
9
- # <tt>config/application.rb</tt>:
11
+ # `config/application.rb`:
10
12
  #
11
13
  # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
12
14
  #
13
- # After restarting the application and re-triggering the deadlock condition,
14
- # the route <tt>/rails/locks</tt> will show a summary of all threads currently
15
- # known to the interlock, which lock level they are holding or awaiting, and
16
- # their current backtrace.
15
+ # After restarting the application and re-triggering the deadlock condition, the
16
+ # route `/rails/locks` will show a summary of all threads currently known to the
17
+ # interlock, which lock level they are holding or awaiting, and their current
18
+ # backtrace.
17
19
  #
18
20
  # Generally a deadlock will be caused by the interlock conflicting with some
19
21
  # other external lock or blocking I/O call. These cannot be automatically
@@ -46,14 +48,14 @@ module ActionDispatch
46
48
  private
47
49
  def render_details(req)
48
50
  threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads|
49
- # The Interlock itself comes to a complete halt as long as this block
50
- # is executing. That gives us a more consistent picture of everything,
51
- # but creates a pretty strong Observer Effect.
51
+ # The Interlock itself comes to a complete halt as long as this block is
52
+ # executing. That gives us a more consistent picture of everything, but creates
53
+ # a pretty strong Observer Effect.
52
54
  #
53
- # Most directly, that means we need to do as little as possible in
54
- # this block. More widely, it means this middleware should remain a
55
- # strictly diagnostic tool (to be used when something has gone wrong),
56
- # and not for any sort of general monitoring.
55
+ # Most directly, that means we need to do as little as possible in this block.
56
+ # More widely, it means this middleware should remain a strictly diagnostic tool
57
+ # (to be used when something has gone wrong), and not for any sort of general
58
+ # monitoring.
57
59
 
58
60
  raw_threads.each.with_index do |(thread, info), idx|
59
61
  info[:index] = idx
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "pp"
4
6
 
5
7
  require "action_view"
@@ -13,17 +15,12 @@ module ActionDispatch
13
15
  paths = RESCUES_TEMPLATE_PATHS.dup
14
16
  lookup_context = ActionView::LookupContext.new(paths)
15
17
  super(lookup_context, assigns, nil)
16
- @exception_wrapper = assigns[:exception_wrapper]
17
18
  end
18
19
 
19
20
  def compiled_method_container
20
21
  self.class
21
22
  end
22
23
 
23
- def error_highlight_available?
24
- @exception_wrapper.error_highlight_available?
25
- end
26
-
27
24
  def debug_params(params)
28
25
  clean_params = params.clone
29
26
  clean_params.delete("action")
@@ -58,6 +55,17 @@ module ActionDispatch
58
55
  end
59
56
  end
60
57
 
58
+ def editor_url(location, line: nil)
59
+ if editor = ActiveSupport::Editor.current
60
+ line ||= location&.lineno
61
+ absolute_path = location&.absolute_path
62
+
63
+ if absolute_path && line && File.exist?(absolute_path)
64
+ editor.url_for(absolute_path, line)
65
+ end
66
+ end
67
+ end
68
+
61
69
  def protect_against_forgery?
62
70
  false
63
71
  end