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
@@ -1,57 +1,70 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionDispatch
4
- # = Action Dispatch \SSL
6
+ # # Action Dispatch SSL
7
+ #
8
+ # This middleware is added to the stack when `config.force_ssl = true`, and is
9
+ # passed the options set in `config.ssl_options`. It does three jobs to enforce
10
+ # secure HTTP requests:
11
+ #
12
+ # 1. **TLS redirect**: Permanently redirects `http://` requests to `https://`
13
+ # with the same URL host, path, etc. Enabled by default. Set
14
+ # `config.ssl_options` to modify the destination URL:
15
+ #
16
+ # config.ssl_options = { redirect: { host: "secure.widgets.com", port: 8080 }`
5
17
  #
6
- # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
7
- # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
8
- # requests:
18
+ # Or set `redirect: false` to disable redirection.
9
19
  #
10
- # 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+
11
- # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+
12
- # to modify the destination URL
13
- # (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set
14
- # <tt>redirect: false</tt> to disable this feature.
20
+ # Requests can opt-out of redirection with `exclude`:
15
21
  #
16
- # Requests can opt-out of redirection with +exclude+:
22
+ # config.ssl_options = { redirect: { exclude: -> request { request.path == "/up" } } }
17
23
  #
18
- # config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
24
+ # Cookies will not be flagged as secure for excluded requests.
19
25
  #
20
- # Cookies will not be flagged as secure for excluded requests.
26
+ # When proxying through a load balancer that terminates SSL, the forwarded
27
+ # request will appear as though it's HTTP instead of HTTPS to the application.
28
+ # This makes redirects and cookie security target HTTP instead of HTTPS.
29
+ # To make the server assume that the proxy already terminated SSL, and
30
+ # that the request really is HTTPS, set `config.assume_ssl` to `true`:
21
31
  #
22
- # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they
23
- # must not be sent along with +http://+ requests. Enabled by default. Set
24
- # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature.
32
+ # config.assume_ssl = true
25
33
  #
26
- # 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember
27
- # this site as TLS-only and automatically redirect non-TLS requests.
28
- # Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable.
34
+ # 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers
35
+ # they must not be sent along with `http://` requests. Enabled by default.
36
+ # Set `config.ssl_options` with `secure_cookies: false` to disable this
37
+ # feature.
29
38
  #
30
- # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
39
+ # 3. **HTTP Strict Transport Security (HSTS)**: Tells the browser to remember
40
+ # this site as TLS-only and automatically redirect non-TLS requests. Enabled
41
+ # by default. Configure `config.ssl_options` with `hsts: false` to disable.
31
42
  #
32
- # * +expires+: How long, in seconds, these settings will stick. The minimum
33
- # required to qualify for browser preload lists is 1 year. Defaults to
34
- # 2 years (recommended).
43
+ # Set `config.ssl_options` with `hsts: { ... }` to configure HSTS:
35
44
  #
36
- # * +subdomains+: Set to +true+ to tell the browser to apply these settings
37
- # to all subdomains. This protects your cookies from interception by a
38
- # vulnerable site on a subdomain. Defaults to +true+.
45
+ # * `expires`: How long, in seconds, these settings will stick. The
46
+ # minimum required to qualify for browser preload lists is 1 year.
47
+ # Defaults to 2 years (recommended).
39
48
  #
40
- # * +preload+: Advertise that this site may be included in browsers'
41
- # preloaded HSTS lists. HSTS protects your site on every visit <i>except the
42
- # first visit</i> since it hasn't seen your HSTS header yet. To close this
43
- # gap, browser vendors include a baked-in list of HSTS-enabled sites.
44
- # Go to https://hstspreload.org to submit your site for inclusion.
45
- # Defaults to +false+.
49
+ # * `subdomains`: Set to `true` to tell the browser to apply these
50
+ # settings to all subdomains. This protects your cookies from
51
+ # interception by a vulnerable site on a subdomain. Defaults to `true`.
52
+ #
53
+ # * `preload`: Advertise that this site may be included in browsers'
54
+ # preloaded HSTS lists. HSTS protects your site on every visit *except
55
+ # the first visit* since it hasn't seen your HSTS header yet. To close
56
+ # this gap, browser vendors include a baked-in list of HSTS-enabled
57
+ # sites. Go to https://hstspreload.org to submit your site for
58
+ # inclusion. Defaults to `false`.
59
+ #
60
+ #
61
+ # To turn off HSTS, omitting the header is not enough. Browsers will
62
+ # remember the original HSTS directive until it expires. Instead, use the
63
+ # header to tell browsers to expire HSTS immediately. Setting `hsts: false`
64
+ # is a shortcut for `hsts: { expires: 0 }`.
46
65
  #
47
- # To turn off HSTS, omitting the header is not enough. Browsers will remember the
48
- # original HSTS directive until it expires. Instead, use the header to tell browsers to
49
- # expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for
50
- # <tt>hsts: { expires: 0 }</tt>.
51
66
  class SSL
52
- # :stopdoc:
53
-
54
- # Default to 2 years as recommended on hstspreload.org.
67
+ # :stopdoc: Default to 2 years as recommended on hstspreload.org.
55
68
  HSTS_EXPIRES_IN = 63072000
56
69
 
57
70
  PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc:
@@ -93,8 +106,8 @@ module ActionDispatch
93
106
 
94
107
  def normalize_hsts_options(options)
95
108
  case options
96
- # Explicitly disabling HSTS clears the existing setting from browsers
97
- # by setting expiry to 0.
109
+ # Explicitly disabling HSTS clears the existing setting from browsers by setting
110
+ # expiry to 0.
98
111
  when false
99
112
  self.class.default_hsts_options.merge(expires: 0)
100
113
  # Default to enabled, with default options.
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/inflector/methods"
4
6
  require "active_support/dependencies"
5
7
 
6
8
  module ActionDispatch
7
- # = Action Dispatch \MiddlewareStack
9
+ # # Action Dispatch MiddlewareStack
8
10
  #
9
- # Read more about {Rails middleware
10
- # stack}[https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack]
11
+ # Read more about [Rails middleware
12
+ # stack](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
11
13
  # in the guides.
12
14
  class MiddlewareStack
13
15
  class Middleware
@@ -47,9 +49,8 @@ module ActionDispatch
47
49
  end
48
50
  end
49
51
 
50
- # This class is used to instrument the execution of a single middleware.
51
- # It proxies the +call+ method transparently and instruments the method
52
- # call.
52
+ # This class is used to instrument the execution of a single middleware. It
53
+ # proxies the `call` method transparently and instruments the method call.
53
54
  class InstrumentationProxy
54
55
  EVENT_NAME = "process_middleware.action_dispatch"
55
56
 
@@ -125,16 +126,16 @@ module ActionDispatch
125
126
 
126
127
  # Deletes a middleware from the middleware stack.
127
128
  #
128
- # Returns the array of middlewares not including the deleted item, or
129
- # returns nil if the target is not found.
129
+ # Returns the array of middlewares not including the deleted item, or returns
130
+ # nil if the target is not found.
130
131
  def delete(target)
131
132
  middlewares.reject! { |m| m.name == target.name }
132
133
  end
133
134
 
134
135
  # Deletes a middleware from the middleware stack.
135
136
  #
136
- # Returns the array of middlewares not including the deleted item, or
137
- # raises +RuntimeError+ if the target is not found.
137
+ # Returns the array of middlewares not including the deleted item, or raises
138
+ # `RuntimeError` if the target is not found.
138
139
  def delete!(target)
139
140
  delete(target) || (raise "No such middleware to remove: #{target.inspect}")
140
141
  end
@@ -1,18 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "rack/utils"
4
6
 
5
7
  module ActionDispatch
6
- # = Action Dispatch \Static
8
+ # # Action Dispatch Static
7
9
  #
8
- # This middleware serves static files from disk, if available.
9
- # If no file is found, it hands off to the main app.
10
+ # This middleware serves static files from disk, if available. If no file is
11
+ # found, it hands off to the main app.
10
12
  #
11
- # In \Rails apps, this middleware is configured to serve assets from
12
- # the +public/+ directory.
13
+ # In Rails apps, this middleware is configured to serve assets from the
14
+ # `public/` directory.
13
15
  #
14
- # Only GET and HEAD requests are served. POST and other HTTP methods
15
- # are handed off to the main app.
16
+ # Only GET and HEAD requests are served. POST and other HTTP methods are handed
17
+ # off to the main app.
16
18
  #
17
19
  # Only files in the root directory are served; path traversal is denied.
18
20
  class Static
@@ -26,31 +28,31 @@ module ActionDispatch
26
28
  end
27
29
  end
28
30
 
29
- # = Action Dispatch \FileHandler
31
+ # # Action Dispatch FileHandler
30
32
  #
31
- # This endpoint serves static files from disk using +Rack::Files+.
33
+ # This endpoint serves static files from disk using `Rack::Files`.
32
34
  #
33
- # URL paths are matched with static files according to expected
34
- # conventions: +path+, +path+.html, +path+/index.html.
35
+ # URL paths are matched with static files according to expected conventions:
36
+ # `path`, `path`.html, `path`/index.html.
35
37
  #
36
- # Precompressed versions of these files are checked first. Brotli (.br)
37
- # and gzip (.gz) files are supported. If +path+.br exists, this
38
- # endpoint returns that file with a <tt>content-encoding: br</tt> header.
38
+ # Precompressed versions of these files are checked first. Brotli (.br) and gzip
39
+ # (.gz) files are supported. If `path`.br exists, this endpoint returns that
40
+ # file with a `content-encoding: br` header.
39
41
  #
40
- # If no matching file is found, this endpoint responds <tt>404 Not Found</tt>.
42
+ # If no matching file is found, this endpoint responds `404 Not Found`.
41
43
  #
42
- # Pass the +root+ directory to search for matching files, an optional
43
- # <tt>index: "index"</tt> to change the default +path+/index.html, and optional
44
- # additional response headers.
44
+ # Pass the `root` directory to search for matching files, an optional `index:
45
+ # "index"` to change the default `path`/index.html, and optional additional
46
+ # response headers.
45
47
  class FileHandler
46
- # +Accept-Encoding+ value -> file extension
48
+ # `Accept-Encoding` value -> file extension
47
49
  PRECOMPRESSED = {
48
50
  "br" => ".br",
49
51
  "gzip" => ".gz",
50
52
  "identity" => nil
51
53
  }
52
54
 
53
- def initialize(root, index: "index", headers: {}, precompressed: %i[ br gzip ], compressible_content_types: /\A(?:text\/|application\/javascript)/)
55
+ def initialize(root, index: "index", headers: {}, precompressed: %i[ br gzip ], compressible_content_types: /\A(?:text\/|application\/javascript|image\/svg\+xml)/)
54
56
  @root = root.chomp("/").b
55
57
  @index = index
56
58
 
@@ -91,11 +93,11 @@ module ActionDispatch
91
93
 
92
94
  # Match a URI path to a static file to be served.
93
95
  #
94
- # Used by the +Static+ class to negotiate a servable file in the
95
- # +public/+ directory (see Static#call).
96
+ # Used by the `Static` class to negotiate a servable file in the `public/`
97
+ # directory (see Static#call).
96
98
  #
97
- # Checks for +path+, +path+.html, and +path+/index.html files,
98
- # in that order, including .br and .gzip compressed extensions.
99
+ # Checks for `path`, `path`.html, and `path`/index.html files, in that order,
100
+ # including .br and .gzip compressed extensions.
99
101
  #
100
102
  # If a matching file is found, the path and necessary response headers
101
103
  # (Content-Type, Content-Encoding) are returned.
@@ -120,11 +122,11 @@ module ActionDispatch
120
122
  def try_precompressed_files(filepath, headers, accept_encoding:)
121
123
  each_precompressed_filepath(filepath) do |content_encoding, precompressed_filepath|
122
124
  if file_readable? precompressed_filepath
123
- # Identity encoding is default, so we skip Accept-Encoding
124
- # negotiation and needn't set Content-Encoding.
125
+ # Identity encoding is default, so we skip Accept-Encoding negotiation and
126
+ # needn't set Content-Encoding.
125
127
  #
126
- # Vary header is expected when we've found other available
127
- # encodings that Accept-Encoding ruled out.
128
+ # Vary header is expected when we've found other available encodings that
129
+ # Accept-Encoding ruled out.
128
130
  if content_encoding == "identity"
129
131
  return precompressed_filepath, headers
130
132
  else
@@ -164,9 +166,9 @@ module ActionDispatch
164
166
  content_type = ::Rack::Mime.mime_type(ext, nil)
165
167
  yield path, content_type || "text/plain"
166
168
 
167
- # Tack on .html and /index.html only for paths that don't have
168
- # an explicit, resolvable file extension. No need to check
169
- # for foo.js.html and foo.js/index.html.
169
+ # Tack on .html and /index.html only for paths that don't have an explicit,
170
+ # resolvable file extension. No need to check for foo.js.html and
171
+ # foo.js/index.html.
170
172
  unless content_type
171
173
  default_ext = ::ActionController::Base.default_static_extension
172
174
  if ext != default_ext
@@ -0,0 +1 @@
1
+ <button onclick="copyAsText.bind(this)()">Copy as text</button>
@@ -11,8 +11,9 @@
11
11
  <tr>
12
12
  <td>
13
13
  <pre class="line_numbers">
14
- <% source_extract[:code].each_key do |line_number| %>
15
- <span><%= line_number -%></span>
14
+ <% source_extract[:code].each_key do |line| %>
15
+ <% file_url = editor_url(source_extract[:trace], line: line) %>
16
+ <span><%= link_to_if file_url, line, file_url -%></span>
16
17
  <% end %>
17
18
  </pre>
18
19
  </td>
@@ -28,9 +29,6 @@
28
29
  </tr>
29
30
  </table>
30
31
  </div>
31
- <%- unless self.error_highlight_available? -%>
32
- <p class="error_highlight_tip">Tip: You may want to add <code>gem 'error_highlight', '&gt;= 0.4.0'</code> into your Gemfile, which will display the fine-grained error location.</p>
33
- <%- end -%>
34
32
  </div>
35
33
  <% end %>
36
34
  <% end %>
@@ -13,13 +13,17 @@
13
13
  <% end %>
14
14
 
15
15
  <% traces.each do |name, trace| %>
16
- <div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
16
+ <div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" class="trace-container" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
17
17
  <code class="traces">
18
18
  <% trace.each do |frame| %>
19
- <a class="trace-frames trace-frames-<%= error_index %>" data-exception-object-id="<%= frame[:exception_object_id] %>" data-frame-id="<%= frame[:id] %>" href="#">
20
- <%= frame[:trace] %>
21
- </a>
22
- <br>
19
+ <div class="trace">
20
+ <% file_url = editor_url(frame[:trace]) %>
21
+ <%= file_url && link_to("✏️", file_url, class: "edit-icon") %>
22
+ <a class="trace-frames trace-frames-<%= error_index %>" data-exception-object-id="<%= frame[:exception_object_id] %>" data-frame-id="<%= frame[:id] %>" href="#">
23
+ <%= frame[:trace] %>
24
+ </a>
25
+ <br>
26
+ </div>
23
27
  <% end %>
24
28
  </code>
25
29
  </div>
@@ -1,4 +1,5 @@
1
1
  <header>
2
+ <%= render "rescues/copy_button" %>
2
3
  <h1>Blocked hosts: <%= @hosts.join(", ") %></h1>
3
4
  </header>
4
5
  <main role="main" id="container">
@@ -1,4 +1,5 @@
1
1
  <header>
2
+ <%= render "rescues/copy_button" %>
2
3
  <h1>
3
4
  <%= @exception_wrapper.exception_class_name %>
4
5
  <% if params_valid? && @request.parameters['controller'] %>
@@ -1,4 +1,5 @@
1
1
  <header role="banner">
2
+ <%= render "rescues/copy_button" %>
2
3
  <h1>
3
4
  <%= @exception.class.to_s %>
4
5
  <% if @request.parameters['controller'] %>
@@ -10,6 +11,9 @@
10
11
  <main role="main" id="container">
11
12
  <h2>
12
13
  <%= h @exception.message %>
14
+ <% if defined?(ActionText) && @exception.message.match?(%r{#{ActionText::RichText.table_name}}) %>
15
+ <br />To resolve this issue run: bin/rails action_text:install
16
+ <% end %>
13
17
  <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
14
18
  <br />To resolve this issue run: bin/rails active_storage:install
15
19
  <% end %>
@@ -4,6 +4,9 @@
4
4
  <% end %>
5
5
 
6
6
  <%= @exception.message %>
7
+ <% if defined?(ActionText) && @exception.message.match?(%r{#{ActionText::RichText.table_name}}) %>
8
+ To resolve this issue run: bin/rails action_text:install
9
+ <% end %>
7
10
  <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
8
11
  To resolve this issue run: bin/rails active_storage:install
9
12
  <% end %>
@@ -38,6 +38,22 @@
38
38
  padding: 0.5em 1.5em;
39
39
  }
40
40
 
41
+ header button {
42
+ appearance: none;
43
+ background-color: hsl(0 0% 0% / 0.2);
44
+ border: 0;
45
+ border-radius: 14px;
46
+ color: white;
47
+ float: right;
48
+ font-weight: 500;
49
+ height: 28px;
50
+ padding-inline: 14px;
51
+ margin: 0.35em 0;
52
+ }
53
+ header button:active {
54
+ background-color: hsl(0 0% 0% / 0.25);
55
+ }
56
+
41
57
  h1 {
42
58
  overflow-wrap: break-word;
43
59
  margin: 0.2em 0;
@@ -54,6 +70,30 @@
54
70
  font-size: 11px;
55
71
  }
56
72
 
73
+ .trace-container {
74
+ margin-top: 10px;
75
+ }
76
+
77
+ code.traces .trace {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 2px;
81
+ }
82
+
83
+ .edit-icon {
84
+ width: 16px;
85
+ height: 16px;
86
+ display: flex;
87
+ font-size: 13px;
88
+ align-items: center;
89
+ justify-content: center;
90
+ text-decoration: none;
91
+ }
92
+
93
+ .edit-icon:hover {
94
+ scale: 1.05;
95
+ }
96
+
57
97
  .response-heading, .request-heading {
58
98
  margin-top: 30px;
59
99
  }
@@ -274,11 +314,21 @@
274
314
  var toggleEnvDump = function() {
275
315
  return toggle('env_dump');
276
316
  }
317
+ var copyAsText = function() {
318
+ const text = document.getElementById("exception-message-for-copy").textContent;
319
+
320
+ navigator.clipboard.writeText(text).then(() => {
321
+ const beforeText = this.innerText;
322
+ this.innerText = "Copied!"
323
+ setTimeout(() => this.innerText = beforeText, 1000)
324
+ })
325
+ }
277
326
  </script>
278
327
  </head>
279
328
  <body>
280
329
 
281
330
  <%= yield %>
331
+ <script type="text/plain" id="exception-message-for-copy"><%= raw @exception_message_for_copy %></script>
282
332
 
283
333
  </body>
284
334
  </html>
@@ -1,4 +1,5 @@
1
1
  <header role="banner">
2
+ <%= render "rescues/copy_button" %>
2
3
  <h1>No view template for interactive request</h1>
3
4
  </header>
4
5
 
@@ -1,4 +1,5 @@
1
1
  <header role="banner">
2
+ <%= render "rescues/copy_button" %>
2
3
  <h1>Template is missing</h1>
3
4
  </header>
4
5
 
@@ -1,4 +1,5 @@
1
1
  <header role="banner">
2
+ <%= render "rescues/copy_button" %>
2
3
  <h1>Routing Error</h1>
3
4
  </header>
4
5
  <main role="main" id="container">
@@ -1,4 +1,5 @@
1
1
  <header role="banner">
2
+ <%= render "rescues/copy_button" %>
2
3
  <h1>
3
4
  <%= @exception_wrapper.exception_name %> in
4
5
  <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
@@ -1,4 +1,5 @@
1
1
  <header role="banner">
2
+ <%= render "rescues/copy_button" %>
2
3
  <h1>Unknown action</h1>
3
4
  </header>
4
5
  <main role="main" id="container">
@@ -158,7 +158,7 @@
158
158
  function buildTr(string) {
159
159
  var tr = document.createElement('tr');
160
160
  var th = document.createElement('th');
161
- th.setAttribute('colspan', 4);
161
+ th.setAttribute('colspan', 5);
162
162
  tr.appendChild(th);
163
163
  th.innerText = string;
164
164
  return tr;
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch"
4
6
  require "action_dispatch/log_subscriber"
7
+ require "action_dispatch/structured_event_subscriber"
5
8
  require "active_support/messages/rotation_configuration"
9
+ require "rails/railtie"
6
10
 
7
11
  module ActionDispatch
8
12
  class Railtie < Rails::Railtie # :nodoc:
@@ -27,6 +31,11 @@ module ActionDispatch
27
31
  config.action_dispatch.request_id_header = ActionDispatch::Constants::X_REQUEST_ID
28
32
  config.action_dispatch.log_rescued_responses = true
29
33
  config.action_dispatch.debug_exception_log_level = :fatal
34
+ config.action_dispatch.strict_freshness = false
35
+
36
+ config.action_dispatch.ignore_leading_brackets = nil
37
+ config.action_dispatch.strict_query_string_separator = nil
38
+ config.action_dispatch.verbose_redirect_logs = false
30
39
 
31
40
  config.action_dispatch.default_headers = {
32
41
  "X-Frame-Options" => "SAMEORIGIN",
@@ -49,11 +58,21 @@ module ActionDispatch
49
58
  ActionDispatch::Http::URL.secure_protocol = app.config.force_ssl
50
59
  ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
51
60
 
61
+ unless app.config.action_dispatch.domain_extractor.nil?
62
+ ActionDispatch::Http::URL.domain_extractor = app.config.action_dispatch.domain_extractor
63
+ end
64
+
65
+ unless app.config.action_dispatch.ignore_leading_brackets.nil?
66
+ ActionDispatch::ParamBuilder.ignore_leading_brackets = app.config.action_dispatch.ignore_leading_brackets
67
+ end
68
+ unless app.config.action_dispatch.strict_query_string_separator.nil?
69
+ ActionDispatch::QueryParser.strict_query_string_separator = app.config.action_dispatch.strict_query_string_separator
70
+ end
71
+
72
+ ActionDispatch.verbose_redirect_logs = app.config.action_dispatch.verbose_redirect_logs
73
+
52
74
  ActiveSupport.on_load(:action_dispatch_request) do
53
75
  self.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
54
- unless app.config.action_dispatch.respond_to?(:return_only_request_media_type_on_content_type)
55
- self.return_only_media_type_on_content_type = app.config.action_dispatch.return_only_request_media_type_on_content_type
56
- end
57
76
  ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
58
77
  end
59
78
 
@@ -70,6 +89,7 @@ module ActionDispatch
70
89
 
71
90
  ActionDispatch::Routing::Mapper.route_source_locations = Rails.env.development?
72
91
 
92
+ ActionDispatch::Http::Cache::Request.strict_freshness = app.config.action_dispatch.strict_freshness
73
93
  ActionDispatch.test_app = app
74
94
  end
75
95
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "rack/session/abstract/id"
4
6
 
5
7
  module ActionDispatch
@@ -107,8 +109,8 @@ module ActionDispatch
107
109
  end
108
110
  end
109
111
 
110
- # Returns value of the key stored in the session or
111
- # +nil+ if the given key is not found in the session.
112
+ # Returns value of the key stored in the session or `nil` if the given key is
113
+ # not found in the session.
112
114
  def [](key)
113
115
  load_for_read!
114
116
  key = key.to_s
@@ -120,8 +122,8 @@ module ActionDispatch
120
122
  end
121
123
  end
122
124
 
123
- # Returns the nested value specified by the sequence of keys, returning
124
- # +nil+ if any intermediate step is +nil+.
125
+ # Returns the nested value specified by the sequence of keys, returning `nil` if
126
+ # any intermediate step is `nil`.
125
127
  def dig(*keys)
126
128
  load_for_read!
127
129
  keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key }
@@ -153,6 +155,7 @@ module ActionDispatch
153
155
  load_for_write!
154
156
  @delegate[key.to_s] = value
155
157
  end
158
+ alias store []=
156
159
 
157
160
  # Clears the session.
158
161
  def clear
@@ -169,14 +172,14 @@ module ActionDispatch
169
172
 
170
173
  # Updates the session with given Hash.
171
174
  #
172
- # session.to_hash
173
- # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"}
175
+ # session.to_hash
176
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"}
174
177
  #
175
- # session.update({ "foo" => "bar" })
176
- # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
178
+ # session.update({ "foo" => "bar" })
179
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
177
180
  #
178
- # session.to_hash
179
- # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
181
+ # session.to_hash
182
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
180
183
  def update(hash)
181
184
  unless hash.respond_to?(:to_hash)
182
185
  raise TypeError, "no implicit conversion of #{hash.class.name} into Hash"
@@ -193,20 +196,20 @@ module ActionDispatch
193
196
  @delegate.delete key.to_s
194
197
  end
195
198
 
196
- # Returns value of the given key from the session, or raises +KeyError+
197
- # if can't find the given key and no default value is set.
198
- # Returns default value if specified.
199
+ # Returns value of the given key from the session, or raises `KeyError` if can't
200
+ # find the given key and no default value is set. Returns default value if
201
+ # specified.
199
202
  #
200
- # session.fetch(:foo)
201
- # # => KeyError: key not found: "foo"
203
+ # session.fetch(:foo)
204
+ # # => KeyError: key not found: "foo"
202
205
  #
203
- # session.fetch(:foo, :bar)
204
- # # => :bar
206
+ # session.fetch(:foo, :bar)
207
+ # # => :bar
205
208
  #
206
- # session.fetch(:foo) do
207
- # :bar
208
- # end
209
- # # => :bar
209
+ # session.fetch(:foo) do
210
+ # :bar
211
+ # end
212
+ # # => :bar
210
213
  def fetch(key, default = Unspecified, &block)
211
214
  load_for_read!
212
215
  if default == Unspecified