actionpack 4.2.10 → 7.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  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 +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  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 +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  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 +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  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 +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  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 +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  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 +425 -497
  65. data/lib/action_controller.rb +44 -22
  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 +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  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 +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  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 +139 -15
  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 +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,123 +1,192 @@
1
- require 'rack/utils'
2
- require 'active_support/core_ext/uri'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "rack/utils"
3
6
 
4
7
  module ActionDispatch
5
- # This middleware returns a file's contents from disk in the body response.
6
- # When initialized it can accept an optional 'Cache-Control' header which
7
- # will be set when a response containing a file's contents is delivered.
8
+ # # Action Dispatch Static
8
9
  #
9
- # This middleware will render the file specified in `env["PATH_INFO"]`
10
- # where the base path is in the +root+ directory. For example if the +root+
11
- # is set to `public/` then a request with `env["PATH_INFO"]` of
12
- # `assets/application.js` will return a response with contents of a file
13
- # located at `public/assets/application.js` if the file exists. If the file
14
- # does not exist a 404 "File not Found" response will be returned.
15
- class FileHandler
16
- def initialize(root, cache_control)
17
- @root = root.chomp('/')
18
- @compiled_root = /^#{Regexp.escape(root)}/
19
- headers = cache_control && { 'Cache-Control' => cache_control }
20
- @file_server = ::Rack::File.new(@root, headers)
10
+ # This middleware serves static files from disk, if available. If no file is
11
+ # found, it hands off to the main app.
12
+ #
13
+ # In Rails apps, this middleware is configured to serve assets from the
14
+ # `public/` directory.
15
+ #
16
+ # Only GET and HEAD requests are served. POST and other HTTP methods are handed
17
+ # off to the main app.
18
+ #
19
+ # Only files in the root directory are served; path traversal is denied.
20
+ class Static
21
+ def initialize(app, path, index: "index", headers: {})
22
+ @app = app
23
+ @file_handler = FileHandler.new(path, index: index, headers: headers)
24
+ end
25
+
26
+ def call(env)
27
+ @file_handler.attempt(env) || @app.call(env)
21
28
  end
29
+ end
22
30
 
23
- def match?(path)
24
- path = URI.parser.unescape(path)
25
- return false unless valid_path?(path)
31
+ # # Action Dispatch FileHandler
32
+ #
33
+ # This endpoint serves static files from disk using `Rack::Files`.
34
+ #
35
+ # URL paths are matched with static files according to expected conventions:
36
+ # `path`, `path`.html, `path`/index.html.
37
+ #
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.
41
+ #
42
+ # If no matching file is found, this endpoint responds `404 Not Found`.
43
+ #
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.
47
+ class FileHandler
48
+ # `Accept-Encoding` value -> file extension
49
+ PRECOMPRESSED = {
50
+ "br" => ".br",
51
+ "gzip" => ".gz",
52
+ "identity" => nil
53
+ }
26
54
 
27
- paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"].map { |v|
28
- Rack::Utils.clean_path_info v
29
- }
55
+ def initialize(root, index: "index", headers: {}, precompressed: %i[ br gzip ], compressible_content_types: /\A(?:text\/|application\/javascript|image\/svg\+xml)/)
56
+ @root = root.chomp("/").b
57
+ @index = index
30
58
 
31
- if match = paths.detect { |p|
32
- path = File.join(@root, p.force_encoding('UTF-8'))
33
- begin
34
- File.file?(path) && File.readable?(path)
35
- rescue SystemCallError
36
- false
37
- end
59
+ @precompressed = Array(precompressed).map(&:to_s) | %w[ identity ]
60
+ @compressible_content_types = compressible_content_types
38
61
 
39
- }
40
- return ::Rack::Utils.escape(match)
41
- end
62
+ @file_server = ::Rack::Files.new(@root, headers)
42
63
  end
43
64
 
44
65
  def call(env)
45
- path = env['PATH_INFO']
46
- gzip_path = gzip_file_path(path)
47
-
48
- if gzip_path && gzip_encoding_accepted?(env)
49
- env['PATH_INFO'] = gzip_path
50
- status, headers, body = @file_server.call(env)
51
- if status == 304
52
- return [status, headers, body]
53
- end
54
- headers['Content-Encoding'] = 'gzip'
55
- headers['Content-Type'] = content_type(path)
56
- else
57
- status, headers, body = @file_server.call(env)
58
- end
66
+ attempt(env) || @file_server.call(env)
67
+ end
59
68
 
60
- headers['Vary'] = 'Accept-Encoding' if gzip_path
69
+ def attempt(env)
70
+ request = Rack::Request.new env
61
71
 
62
- return [status, headers, body]
63
- ensure
64
- env['PATH_INFO'] = path
72
+ if request.get? || request.head?
73
+ if found = find_file(request.path_info, accept_encoding: request.accept_encoding)
74
+ serve request, *found
75
+ end
76
+ end
65
77
  end
66
78
 
67
79
  private
68
- def ext
69
- ::ActionController::Base.default_static_extension
80
+ def serve(request, filepath, content_headers)
81
+ original, request.path_info =
82
+ request.path_info, ::Rack::Utils.escape_path(filepath).b
83
+
84
+ @file_server.call(request.env).tap do |status, headers, body|
85
+ # Omit content-encoding/type/etc headers for 304 Not Modified
86
+ if status != 304
87
+ headers.update(content_headers)
88
+ end
89
+ end
90
+ ensure
91
+ request.path_info = original
70
92
  end
71
93
 
72
- def content_type(path)
73
- ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
94
+ # Match a URI path to a static file to be served.
95
+ #
96
+ # Used by the `Static` class to negotiate a servable file in the `public/`
97
+ # directory (see Static#call).
98
+ #
99
+ # Checks for `path`, `path`.html, and `path`/index.html files, in that order,
100
+ # including .br and .gzip compressed extensions.
101
+ #
102
+ # If a matching file is found, the path and necessary response headers
103
+ # (Content-Type, Content-Encoding) are returned.
104
+ def find_file(path_info, accept_encoding:)
105
+ each_candidate_filepath(path_info) do |filepath, content_type|
106
+ if response = try_files(filepath, content_type, accept_encoding: accept_encoding)
107
+ return response
108
+ end
109
+ end
74
110
  end
75
111
 
76
- def gzip_encoding_accepted?(env)
77
- env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
112
+ def try_files(filepath, content_type, accept_encoding:)
113
+ headers = { Rack::CONTENT_TYPE => content_type }
114
+
115
+ if compressible? content_type
116
+ try_precompressed_files filepath, headers, accept_encoding: accept_encoding
117
+ elsif file_readable? filepath
118
+ [ filepath, headers ]
119
+ end
78
120
  end
79
121
 
80
- def gzip_file_path(path)
81
- can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
82
- gzip_path = "#{path}.gz"
83
- if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape(gzip_path)))
84
- gzip_path
85
- else
86
- false
122
+ def try_precompressed_files(filepath, headers, accept_encoding:)
123
+ each_precompressed_filepath(filepath) do |content_encoding, precompressed_filepath|
124
+ if file_readable? precompressed_filepath
125
+ # Identity encoding is default, so we skip Accept-Encoding negotiation and
126
+ # needn't set Content-Encoding.
127
+ #
128
+ # Vary header is expected when we've found other available encodings that
129
+ # Accept-Encoding ruled out.
130
+ if content_encoding == "identity"
131
+ return precompressed_filepath, headers
132
+ else
133
+ headers[ActionDispatch::Constants::VARY] = "accept-encoding"
134
+
135
+ if accept_encoding.any? { |enc, _| /\b#{content_encoding}\b/i.match?(enc) }
136
+ headers[ActionDispatch::Constants::CONTENT_ENCODING] = content_encoding
137
+ return precompressed_filepath, headers
138
+ end
139
+ end
140
+ end
87
141
  end
88
142
  end
89
143
 
90
- def valid_path?(path)
91
- path.valid_encoding? && !path.include?("\0")
144
+ def file_readable?(path)
145
+ file_path = File.join(@root, path.b)
146
+ File.file?(file_path) && File.readable?(file_path)
92
147
  end
93
- end
94
148
 
95
- # This middleware will attempt to return the contents of a file's body from
96
- # disk in the response. If a file is not found on disk, the request will be
97
- # delegated to the application stack. This middleware is commonly initialized
98
- # to serve assets from a server's `public/` directory.
99
- #
100
- # This middleware verifies the path to ensure that only files
101
- # living in the root directory can be rendered. A request cannot
102
- # produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
103
- # requests will result in a file being returned.
104
- class Static
105
- def initialize(app, path, cache_control=nil)
106
- @app = app
107
- @file_handler = FileHandler.new(path, cache_control)
108
- end
149
+ def compressible?(content_type)
150
+ @compressible_content_types.match?(content_type)
151
+ end
109
152
 
110
- def call(env)
111
- case env['REQUEST_METHOD']
112
- when 'GET', 'HEAD'
113
- path = env['PATH_INFO'].chomp('/')
114
- if match = @file_handler.match?(path)
115
- env["PATH_INFO"] = match
116
- return @file_handler.call(env)
153
+ def each_precompressed_filepath(filepath)
154
+ @precompressed.each do |content_encoding|
155
+ precompressed_ext = PRECOMPRESSED.fetch(content_encoding)
156
+ yield content_encoding, "#{filepath}#{precompressed_ext}"
117
157
  end
158
+
159
+ nil
118
160
  end
119
161
 
120
- @app.call(env)
121
- end
162
+ def each_candidate_filepath(path_info)
163
+ return unless path = clean_path(path_info)
164
+
165
+ ext = ::File.extname(path)
166
+ content_type = ::Rack::Mime.mime_type(ext, nil)
167
+ yield path, content_type || "text/plain"
168
+
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.
172
+ unless content_type
173
+ default_ext = ::ActionController::Base.default_static_extension
174
+ if ext != default_ext
175
+ default_content_type = ::Rack::Mime.mime_type(default_ext, "text/plain")
176
+
177
+ yield "#{path}#{default_ext}", default_content_type
178
+ yield "#{path}/#{@index}#{default_ext}", default_content_type
179
+ end
180
+ end
181
+
182
+ nil
183
+ end
184
+
185
+ def clean_path(path_info)
186
+ path = ::Rack::Utils.unescape_path path_info.chomp("/")
187
+ if ::Rack::Utils.valid_path? path
188
+ ::Rack::Utils.clean_path_info path
189
+ end
190
+ end
122
191
  end
123
192
  end
@@ -0,0 +1,13 @@
1
+ <% actions = exception_wrapper.actions %>
2
+
3
+ <% if actions.any? %>
4
+ <div class="actions">
5
+ <% actions.each do |action, _| %>
6
+ <%= button_to action, ActionDispatch::ActionableExceptions.endpoint, params: {
7
+ error: exception_wrapper.exception_class_name,
8
+ action: action,
9
+ location: request.path
10
+ } %>
11
+ <% end %>
12
+ </div>
13
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <% if exception_wrapper.has_corrections? %>
2
+ <div class="exception-message">
3
+ <%= simple_format h(exception_wrapper.original_message), { class: "message" }, wrapper_tag: "div" %>
4
+ </div>
5
+ <%
6
+ # The 'did_you_mean' gem can raise exceptions when calling #corrections on
7
+ # the exception. If it does there are no corrections to show.
8
+ corrections = exception_wrapper.corrections rescue []
9
+ %>
10
+ <% if corrections.any? %>
11
+ <b>Did you mean?</b>
12
+ <ul>
13
+ <% corrections.each do |correction| %>
14
+ <li class="correction"><%= h correction %></li>
15
+ <% end %>
16
+ </ul>
17
+ <% end %>
18
+ <% else %>
19
+ <div class="exception-message">
20
+ <%= simple_format h(exception_wrapper.message), { class: "message" }, wrapper_tag: "div" %>
21
+ </div>
22
+ <% end %>
@@ -1,34 +1,17 @@
1
- <% unless @exception.blamed_files.blank? %>
2
- <% if (hide = @exception.blamed_files.length > 8) %>
3
- <a href="#" onclick="return toggleTrace()">Toggle blamed files</a>
4
- <% end %>
5
- <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
1
+ <h2 class="request-heading">Request</h2>
2
+ <% if params_valid? %>
3
+ <p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre>
6
4
  <% end %>
7
5
 
8
- <%
9
- clean_params = @request.filtered_parameters.clone
10
- clean_params.delete("action")
11
- clean_params.delete("controller")
12
-
13
- request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
14
-
15
- def debug_hash(object)
16
- object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
17
- end unless self.class.method_defined?(:debug_hash)
18
- %>
19
-
20
- <h2 style="margin-top: 30px">Request</h2>
21
- <p><b>Parameters</b>:</p> <pre><%= request_dump %></pre>
22
-
23
6
  <div class="details">
24
7
  <div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
25
- <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
8
+ <div id="session_dump" class="hidden"><pre><%= debug_hash @request.session %></pre></div>
26
9
  </div>
27
10
 
28
11
  <div class="details">
29
12
  <div class="summary"><a href="#" onclick="return toggleEnvDump()">Toggle env dump</a></div>
30
- <div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
13
+ <div id="env_dump" class="hidden"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
31
14
  </div>
32
15
 
33
- <h2 style="margin-top: 30px">Response</h2>
34
- <p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
16
+ <h2 class="response-heading">Response</h2>
17
+ <p><b>Headers</b>:</p> <pre><%= debug_headers(defined?(@response) ? @response.headers : {}) %></pre>
@@ -1,5 +1,5 @@
1
1
  <%
2
- clean_params = @request.filtered_parameters.clone
2
+ clean_params = params_valid? ? @request.filtered_parameters.clone : {}
3
3
  clean_params.delete("action")
4
4
  clean_params.delete("controller")
5
5
 
@@ -0,0 +1,36 @@
1
+ <% error_index = local_assigns[:error_index] || 0 %>
2
+
3
+ <% source_extracts.each_with_index do |source_extract, index| %>
4
+ <% if source_extract[:code] %>
5
+ <div class="source <%= "hidden" if show_source_idx != index %>" id="frame-source-<%= error_index %>-<%= index %>">
6
+ <div class="info">
7
+ Extracted source (around line <strong>#<%= source_extract[:line_number] %></strong>):
8
+ </div>
9
+ <div class="data">
10
+ <table cellpadding="0" cellspacing="0" class="lines">
11
+ <tr>
12
+ <td>
13
+ <pre class="line_numbers">
14
+ <% source_extract[:code].each_key do |line_number| %>
15
+ <span><%= line_number -%></span>
16
+ <% end %>
17
+ </pre>
18
+ </td>
19
+ <td width="100%">
20
+ <pre>
21
+ <% source_extract[:code].each do |line, source| -%>
22
+ <div class="line<%= " active" if line == source_extract[:line_number] -%>"><% if source.is_a?(Array) -%><%= source[0] -%><span class="error_highlight"><%= source[1] -%></span><%= source[2] -%>
23
+ <% else -%>
24
+ <%= source -%>
25
+ <% end -%></div><% end -%>
26
+ </pre>
27
+ </td>
28
+ </tr>
29
+ </table>
30
+ </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
+ </div>
35
+ <% end %>
36
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <% @source_extracts.first(3).each do |source_extract| %>
2
+ <% if source_extract[:code] %>
3
+ Extracted source (around line #<%= source_extract[:line_number] %>):
4
+
5
+ <% source_extract[:code].each do |line, source| -%>
6
+ <%= line == source_extract[:line_number] ? "*#{line}" : "##{line}" -%> <%= source -%><% end -%>
7
+ <% end %>
8
+ <% end %>
@@ -1,52 +1,62 @@
1
- <% names = @traces.keys %>
1
+ <% names = traces.keys %>
2
+ <% error_index = local_assigns[:error_index] || 0 %>
2
3
 
3
4
  <p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
4
5
 
5
- <div id="traces">
6
+ <div id="traces-<%= error_index %>">
6
7
  <% names.each do |name| %>
7
8
  <%
8
- show = "show('#{name.gsub(/\s/, '-')}');"
9
- hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
9
+ show = "show('#{name.gsub(/\s/, '-')}-#{error_index}');"
10
+ hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}-#{error_index}');"}
10
11
  %>
11
12
  <a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
12
13
  <% end %>
13
14
 
14
- <% @traces.each do |name, trace| %>
15
- <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;">
16
- <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
15
+ <% traces.each do |name, trace| %>
16
+ <div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
17
+ <code class="traces">
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>
23
+ <% end %>
24
+ </code>
17
25
  </div>
18
26
  <% end %>
19
27
 
20
- <script type="text/javascript">
21
- var traceFrames = document.getElementsByClassName('trace-frames');
22
- var selectedFrame, currentSource = document.getElementById('frame-source-0');
23
-
24
- // Add click listeners for all stack frames
25
- for (var i = 0; i < traceFrames.length; i++) {
26
- traceFrames[i].addEventListener('click', function(e) {
27
- e.preventDefault();
28
- var target = e.target;
29
- var frame_id = target.dataset.frameId;
30
-
31
- if (selectedFrame) {
32
- selectedFrame.className = selectedFrame.className.replace("selected", "");
33
- }
34
-
35
- target.className += " selected";
36
- selectedFrame = target;
37
-
38
- // Change the extracted source code
39
- changeSourceExtract(frame_id);
40
- });
41
-
42
- function changeSourceExtract(frame_id) {
43
- var el = document.getElementById('frame-source-' + frame_id);
44
- if (currentSource && el) {
45
- currentSource.className += " hidden";
46
- el.className = el.className.replace(" hidden", "");
47
- currentSource = el;
28
+ <script>
29
+ (function() {
30
+ var traceFrames = document.getElementsByClassName('trace-frames-<%= error_index %>');
31
+ var selectedFrame, currentSource = document.getElementById('frame-source-<%= error_index %>-0');
32
+
33
+ // Add click listeners for all stack frames
34
+ for (var i = 0; i < traceFrames.length; i++) {
35
+ traceFrames[i].addEventListener('click', function(e) {
36
+ e.preventDefault();
37
+ var target = e.target;
38
+ var frame_id = target.dataset.frameId;
39
+
40
+ if (selectedFrame) {
41
+ selectedFrame.className = selectedFrame.className.replace("selected", "");
42
+ }
43
+
44
+ target.className += " selected";
45
+ selectedFrame = target;
46
+
47
+ // Change the extracted source code
48
+ changeSourceExtract(frame_id);
49
+ });
50
+
51
+ function changeSourceExtract(frame_id) {
52
+ var el = document.getElementById('frame-source-<%= error_index %>-' + frame_id);
53
+ if (currentSource && el) {
54
+ currentSource.className += " hidden";
55
+ el.className = el.className.replace(" hidden", "");
56
+ currentSource = el;
57
+ }
48
58
  }
49
59
  }
50
- }
60
+ })();
51
61
  </script>
52
62
  </div>
@@ -0,0 +1,12 @@
1
+ <header>
2
+ <h1>Blocked hosts: <%= @hosts.join(", ") %></h1>
3
+ </header>
4
+ <main role="main" id="container">
5
+ <h2>To allow requests to these hosts, make sure they are valid hostnames (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:</h2>
6
+ <pre>
7
+ <% @hosts.each do |host| %>
8
+ config.hosts &lt;&lt; "<%= host %>"
9
+ <% end %>
10
+ </pre>
11
+ <p>For more details view: <a href="https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization">the Host Authorization guide</a></p>
12
+ </main>
@@ -0,0 +1,9 @@
1
+ Blocked hosts: <%= @hosts.join(", ") %>
2
+
3
+ To allow requests to these hosts, make sure they are valid hostnames (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:
4
+
5
+ <% @hosts.each do |host| %>
6
+ config.hosts << "<%= host %>"
7
+ <% end %>
8
+
9
+ For more details on host authorization view: https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization
@@ -1,16 +1,35 @@
1
1
  <header>
2
2
  <h1>
3
- <%= @exception.class.to_s %>
4
- <% if @request.parameters['controller'] %>
3
+ <%= @exception_wrapper.exception_class_name %>
4
+ <% if params_valid? && @request.parameters['controller'] %>
5
5
  in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
6
6
  <% end %>
7
7
  </h1>
8
8
  </header>
9
9
 
10
- <div id="container">
11
- <h2><%= h @exception.message %></h2>
10
+ <main role="main" id="container">
11
+ <%= render "rescues/message_and_suggestions", exception: @exception, exception_wrapper: @exception_wrapper %>
12
+ <%= render "rescues/actions", exception: @exception, request: @request, exception_wrapper: @exception_wrapper %>
13
+
14
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx, error_index: 0 %>
15
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show, error_index: 0 %>
16
+
17
+ <% if @exception_wrapper.has_cause? %>
18
+ <h2>Exception Causes</h2>
19
+ <% end %>
20
+
21
+ <% @exception_wrapper.wrapped_causes.each.with_index(1) do |wrapper, index| %>
22
+ <div class="details">
23
+ <a class="summary" href="#" onclick="return toggle(<%= wrapper.exception_id %>)">
24
+ <%= wrapper.exception_class_name %>: <%= h wrapper.message %>
25
+ </a>
26
+ </div>
27
+
28
+ <div id="<%= wrapper.exception_id %>" class="hidden">
29
+ <%= render "rescues/source", source_extracts: wrapper.source_extracts, show_source_idx: wrapper.source_to_show_id, error_index: index %>
30
+ <%= render "rescues/trace", traces: wrapper.traces, trace_to_show: wrapper.trace_to_show, error_index: index %>
31
+ </div>
32
+ <% end %>
12
33
 
13
- <%= render template: "rescues/_source" %>
14
- <%= render template: "rescues/_trace" %>
15
34
  <%= render template: "rescues/_request_and_response" %>
16
- </div>
35
+ </main>
@@ -1,9 +1,9 @@
1
- <%= @exception.class.to_s %><%
2
- if @request.parameters['controller']
1
+ <%= @exception_wrapper.exception_class_name %><%
2
+ if params_valid? && @request.parameters['controller']
3
3
  %> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
4
4
  <% end %>
5
5
 
6
- <%= @exception.message %>
6
+ <%= @exception_wrapper.message %>
7
7
  <%= render template: "rescues/_source" %>
8
8
  <%= render template: "rescues/_trace" %>
9
9
  <%= render template: "rescues/_request_and_response" %>
@@ -0,0 +1,24 @@
1
+ <header role="banner">
2
+ <h1>
3
+ <%= @exception.class.to_s %>
4
+ <% if @request.parameters['controller'] %>
5
+ in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
6
+ <% end %>
7
+ </h1>
8
+ </header>
9
+
10
+ <main role="main" id="container">
11
+ <h2>
12
+ <%= h @exception.message %>
13
+ <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
14
+ <br />To resolve this issue run: bin/rails active_storage:install
15
+ <% end %>
16
+ <% if defined?(ActionMailbox) && @exception.message.match?(%r{#{ActionMailbox::InboundEmail.table_name}}) %>
17
+ <br />To resolve this issue run: bin/rails action_mailbox:install
18
+ <% end %>
19
+ </h2>
20
+
21
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
22
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
23
+ <%= render template: "rescues/_request_and_response" %>
24
+ </main>
@@ -0,0 +1,16 @@
1
+ <%= @exception.class.to_s %><%
2
+ if @request.parameters['controller']
3
+ %> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
4
+ <% end %>
5
+
6
+ <%= @exception.message %>
7
+ <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
8
+ To resolve this issue run: bin/rails active_storage:install
9
+ <% end %>
10
+ <% if defined?(ActionMailbox) && @exception.message.match?(%r{#{ActionMailbox::InboundEmail.table_name}}) %>
11
+ To resolve this issue run: bin/rails action_mailbox:install
12
+ <% end %>
13
+
14
+ <%= render template: "rescues/_source" %>
15
+ <%= render template: "rescues/_trace" %>
16
+ <%= render template: "rescues/_request_and_response" %>