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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +86 -600
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -14
- data/lib/abstract_controller/asset_paths.rb +5 -1
- data/lib/abstract_controller/base.rb +166 -136
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +126 -57
- data/lib/abstract_controller/collector.rb +13 -15
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +181 -132
- data/lib/abstract_controller/logger.rb +5 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
- data/lib/abstract_controller/rendering.rb +56 -56
- data/lib/abstract_controller/translation.rb +29 -15
- data/lib/abstract_controller/url_for.rb +15 -11
- data/lib/abstract_controller.rb +21 -5
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +154 -0
- data/lib/action_controller/base.rb +219 -155
- data/lib/action_controller/caching.rb +28 -68
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +35 -22
- data/lib/action_controller/metal/allow_browser.rb +119 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +259 -122
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +9 -5
- data/lib/action_controller/metal/data_streaming.rb +87 -104
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
- data/lib/action_controller/metal/exceptions.rb +71 -24
- data/lib/action_controller/metal/flash.rb +26 -19
- data/lib/action_controller/metal/head.rb +45 -36
- data/lib/action_controller/metal/helpers.rb +80 -64
- data/lib/action_controller/metal/http_authentication.rb +297 -244
- data/lib/action_controller/metal/implicit_render.rb +57 -9
- data/lib/action_controller/metal/instrumentation.rb +76 -64
- data/lib/action_controller/metal/live.rb +238 -176
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +177 -166
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +145 -118
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +203 -64
- data/lib/action_controller/metal/renderers.rb +108 -65
- data/lib/action_controller/metal/rendering.rb +216 -56
- data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
- data/lib/action_controller/metal/rescue.rb +19 -21
- data/lib/action_controller/metal/streaming.rb +179 -138
- data/lib/action_controller/metal/strong_parameters.rb +1058 -382
- data/lib/action_controller/metal/testing.rb +11 -17
- data/lib/action_controller/metal/url_for.rb +37 -21
- data/lib/action_controller/metal.rb +236 -138
- data/lib/action_controller/railtie.rb +89 -11
- data/lib/action_controller/railties/helpers.rb +5 -1
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +425 -497
- data/lib/action_controller.rb +44 -22
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +119 -63
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +364 -0
- data/lib/action_dispatch/http/filter_parameters.rb +36 -34
- data/lib/action_dispatch/http/filter_redirect.rb +24 -12
- data/lib/action_dispatch/http/headers.rb +66 -31
- data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
- data/lib/action_dispatch/http/mime_type.rb +196 -136
- data/lib/action_dispatch/http/mime_types.rb +25 -7
- data/lib/action_dispatch/http/parameters.rb +97 -45
- data/lib/action_dispatch/http/permissions_policy.rb +187 -0
- data/lib/action_dispatch/http/rack_cache.rb +6 -0
- data/lib/action_dispatch/http/request.rb +299 -170
- data/lib/action_dispatch/http/response.rb +311 -160
- data/lib/action_dispatch/http/upload.rb +52 -23
- data/lib/action_dispatch/http/url.rb +201 -125
- data/lib/action_dispatch/journey/formatter.rb +110 -50
- data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
- data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
- data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
- data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
- data/lib/action_dispatch/journey/nodes/node.rb +100 -20
- data/lib/action_dispatch/journey/parser.rb +19 -17
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +14 -4
- data/lib/action_dispatch/journey/path/pattern.rb +79 -63
- data/lib/action_dispatch/journey/route.rb +108 -44
- data/lib/action_dispatch/journey/router/utils.rb +41 -29
- data/lib/action_dispatch/journey/router.rb +64 -57
- data/lib/action_dispatch/journey/routes.rb +23 -21
- data/lib/action_dispatch/journey/scanner.rb +28 -17
- data/lib/action_dispatch/journey/visitors.rb +100 -54
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +7 -6
- data/lib/action_dispatch/middleware/cookies.rb +471 -328
- data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +143 -101
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/reloader.rb +10 -92
- data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
- data/lib/action_dispatch/middleware/request_id.rb +29 -15
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
- data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
- data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
- data/lib/action_dispatch/middleware/ssl.rb +134 -36
- data/lib/action_dispatch/middleware/stack.rb +109 -44
- data/lib/action_dispatch/middleware/static.rb +159 -90
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
- data/lib/action_dispatch/railtie.rb +44 -16
- data/lib/action_dispatch/request/session.rb +159 -69
- data/lib/action_dispatch/request/utils.rb +97 -23
- data/lib/action_dispatch/routing/endpoint.rb +11 -2
- data/lib/action_dispatch/routing/inspector.rb +195 -106
- data/lib/action_dispatch/routing/mapper.rb +1338 -955
- data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
- data/lib/action_dispatch/routing/redirection.rb +78 -51
- data/lib/action_dispatch/routing/route_set.rb +460 -374
- data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
- data/lib/action_dispatch/routing/url_for.rb +172 -124
- data/lib/action_dispatch/routing.rb +159 -158
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +84 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +71 -39
- data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
- data/lib/action_dispatch/testing/assertions.rb +9 -6
- data/lib/action_dispatch/testing/integration.rb +486 -306
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +35 -22
- data/lib/action_dispatch/testing/test_request.rb +29 -34
- data/lib/action_dispatch/testing/test_response.rb +48 -15
- data/lib/action_dispatch.rb +82 -40
- data/lib/action_pack/gem_version.rb +8 -4
- data/lib/action_pack/version.rb +6 -2
- data/lib/action_pack.rb +21 -18
- metadata +146 -56
- data/lib/action_controller/caching/fragments.rb +0 -103
- data/lib/action_controller/metal/force_ssl.rb +0 -97
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/http/parameter_filter.rb +0 -72
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionController
|
6
|
+
# # Action Controller Form Builder
|
7
|
+
#
|
8
|
+
# Override the default form builder for all views rendered by this controller
|
9
|
+
# and any of its descendants. Accepts a subclass of
|
10
|
+
# ActionView::Helpers::FormBuilder.
|
11
|
+
#
|
12
|
+
# For example, given a form builder:
|
13
|
+
#
|
14
|
+
# class AdminFormBuilder < ActionView::Helpers::FormBuilder
|
15
|
+
# def special_field(name)
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# The controller specifies a form builder as its default:
|
20
|
+
#
|
21
|
+
# class AdminAreaController < ApplicationController
|
22
|
+
# default_form_builder AdminFormBuilder
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# Then in the view any form using `form_for` will be an instance of the
|
26
|
+
# specified form builder:
|
27
|
+
#
|
28
|
+
# <%= form_for(@instance) do |builder| %>
|
29
|
+
# <%= builder.special_field(:name) %>
|
30
|
+
# <% end %>
|
31
|
+
module FormBuilder
|
32
|
+
extend ActiveSupport::Concern
|
33
|
+
|
34
|
+
included do
|
35
|
+
class_attribute :_default_form_builder, instance_accessor: false
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
# Set the form builder to be used as the default for all forms in the views
|
40
|
+
# rendered by this controller and its subclasses.
|
41
|
+
#
|
42
|
+
# #### Parameters
|
43
|
+
# * `builder` - Default form builder, an instance of
|
44
|
+
# ActionView::Helpers::FormBuilder
|
45
|
+
def default_form_builder(builder)
|
46
|
+
self._default_form_builder = builder
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Default form builder for the controller
|
51
|
+
def default_form_builder
|
52
|
+
self.class._default_form_builder
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
1
5
|
module ActionController
|
2
6
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
7
|
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
@@ -6,71 +10,80 @@ module ActionController
|
|
6
10
|
return unless logger.info?
|
7
11
|
|
8
12
|
payload = event.payload
|
9
|
-
params
|
13
|
+
params = {}
|
14
|
+
payload[:params].each_pair do |k, v|
|
15
|
+
params[k] = v unless INTERNAL_PARAMS.include?(k)
|
16
|
+
end
|
10
17
|
format = payload[:format]
|
11
18
|
format = format.to_s.upcase if format.is_a?(Symbol)
|
19
|
+
format = "*/*" if format.nil?
|
12
20
|
|
13
21
|
info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
|
14
22
|
info " Parameters: #{params.inspect}" unless params.empty?
|
15
23
|
end
|
24
|
+
subscribe_log_level :start_processing, :info
|
16
25
|
|
17
26
|
def process_action(event)
|
18
27
|
info do
|
19
|
-
payload
|
28
|
+
payload = event.payload
|
20
29
|
additions = ActionController::Base.log_process_action(payload)
|
21
|
-
|
22
30
|
status = payload[:status]
|
23
|
-
|
24
|
-
|
31
|
+
|
32
|
+
if status.nil? && (exception_class_name = payload[:exception]&.first)
|
25
33
|
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
|
26
34
|
end
|
27
|
-
|
28
|
-
|
35
|
+
|
36
|
+
additions << "GC: #{event.gc_time.round(1)}ms"
|
37
|
+
|
38
|
+
message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" \
|
39
|
+
" (#{additions.join(" | ")})"
|
40
|
+
message << "\n\n" if defined?(Rails.env) && Rails.env.development?
|
41
|
+
|
29
42
|
message
|
30
43
|
end
|
31
44
|
end
|
45
|
+
subscribe_log_level :process_action, :info
|
32
46
|
|
33
47
|
def halted_callback(event)
|
34
48
|
info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" }
|
35
49
|
end
|
50
|
+
subscribe_log_level :halted_callback, :info
|
36
51
|
|
37
52
|
def send_file(event)
|
38
53
|
info { "Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)" }
|
39
54
|
end
|
55
|
+
subscribe_log_level :send_file, :info
|
40
56
|
|
41
57
|
def redirect_to(event)
|
42
58
|
info { "Redirected to #{event.payload[:location]}" }
|
43
59
|
end
|
60
|
+
subscribe_log_level :redirect_to, :info
|
44
61
|
|
45
62
|
def send_data(event)
|
46
63
|
info { "Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)" }
|
47
64
|
end
|
65
|
+
subscribe_log_level :send_data, :info
|
48
66
|
|
49
67
|
def unpermitted_parameters(event)
|
50
68
|
debug do
|
51
69
|
unpermitted_keys = event.payload[:keys]
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
def deep_munge(event)
|
57
|
-
debug do
|
58
|
-
"Value for params[:#{event.payload[:keys].join('][:')}] was set "\
|
59
|
-
"to nil, because it was one of [], [null] or [null, null, ...]. "\
|
60
|
-
"Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation "\
|
61
|
-
"for more information."\
|
70
|
+
display_unpermitted_keys = unpermitted_keys.map { |e| ":#{e}" }.join(", ")
|
71
|
+
context = event.payload[:context].map { |k, v| "#{k}: #{v}" }.join(", ")
|
72
|
+
color("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{display_unpermitted_keys}. Context: { #{context} }", RED)
|
62
73
|
end
|
63
74
|
end
|
75
|
+
subscribe_log_level :unpermitted_parameters, :debug
|
64
76
|
|
65
|
-
%w(write_fragment read_fragment exist_fragment?
|
66
|
-
expire_fragment expire_page write_page).each do |method|
|
77
|
+
%w(write_fragment read_fragment exist_fragment? expire_fragment).each do |method|
|
67
78
|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
79
|
+
# frozen_string_literal: true
|
68
80
|
def #{method}(event)
|
69
|
-
return unless
|
70
|
-
|
81
|
+
return unless ActionController::Base.enable_fragment_cache_logging
|
82
|
+
key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path])
|
71
83
|
human_name = #{method.to_s.humanize.inspect}
|
72
|
-
info("\#{human_name} \#{
|
84
|
+
info("\#{human_name} \#{key} (\#{event.duration.round(1)}ms)")
|
73
85
|
end
|
86
|
+
subscribe_log_level :#{method}, :info
|
74
87
|
METHOD
|
75
88
|
end
|
76
89
|
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionController # :nodoc:
|
6
|
+
module AllowBrowser
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Specify the browser versions that will be allowed to access all actions (or
|
11
|
+
# some, as limited by `only:` or `except:`). Only browsers matched in the hash
|
12
|
+
# or named set passed to `versions:` will be blocked if they're below the
|
13
|
+
# versions specified. This means that all other browsers, as well as agents that
|
14
|
+
# aren't reporting a user-agent header, will be allowed access.
|
15
|
+
#
|
16
|
+
# A browser that's blocked will by default be served the file in
|
17
|
+
# public/406-unsupported-browser.html with a HTTP status code of "406 Not
|
18
|
+
# Acceptable".
|
19
|
+
#
|
20
|
+
# In addition to specifically named browser versions, you can also pass
|
21
|
+
# `:modern` as the set to restrict support to browsers natively supporting webp
|
22
|
+
# images, web push, badges, import maps, CSS nesting, and CSS :has. This
|
23
|
+
# includes Safari 17.2+, Chrome 120+, Firefox 121+, Opera 106+.
|
24
|
+
#
|
25
|
+
# You can use https://caniuse.com to check for browser versions supporting the
|
26
|
+
# features you use.
|
27
|
+
#
|
28
|
+
# You can use `ActiveSupport::Notifications` to subscribe to events of browsers
|
29
|
+
# being blocked using the `browser_block.action_controller` event name.
|
30
|
+
#
|
31
|
+
# Examples:
|
32
|
+
#
|
33
|
+
# class ApplicationController < ActionController::Base
|
34
|
+
# # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has
|
35
|
+
# allow_browser versions: :modern
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# class ApplicationController < ActionController::Base
|
39
|
+
# # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
|
40
|
+
# allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# class MessagesController < ApplicationController
|
44
|
+
# # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
|
45
|
+
# allow_browser versions: { opera: 104, chrome: 119 }, only: :show
|
46
|
+
# end
|
47
|
+
def allow_browser(versions:, block: -> { render file: Rails.root.join("public/406-unsupported-browser.html"), layout: false, status: :not_acceptable }, **options)
|
48
|
+
before_action -> { allow_browser(versions: versions, block: block) }, **options
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def allow_browser(versions:, block:)
|
54
|
+
require "useragent"
|
55
|
+
|
56
|
+
if BrowserBlocker.new(request, versions: versions).blocked?
|
57
|
+
ActiveSupport::Notifications.instrument("browser_block.action_controller", request: request, versions: versions) do
|
58
|
+
instance_exec(&block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class BrowserBlocker
|
64
|
+
SETS = {
|
65
|
+
modern: { safari: 17.2, chrome: 120, firefox: 121, opera: 106, ie: false }
|
66
|
+
}
|
67
|
+
|
68
|
+
attr_reader :request, :versions
|
69
|
+
|
70
|
+
def initialize(request, versions:)
|
71
|
+
@request, @versions = request, versions
|
72
|
+
end
|
73
|
+
|
74
|
+
def blocked?
|
75
|
+
user_agent_version_reported? && unsupported_browser?
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def parsed_user_agent
|
80
|
+
@parsed_user_agent ||= UserAgent.parse(request.user_agent)
|
81
|
+
end
|
82
|
+
|
83
|
+
def user_agent_version_reported?
|
84
|
+
request.user_agent.present? && parsed_user_agent.version.to_s.present?
|
85
|
+
end
|
86
|
+
|
87
|
+
def unsupported_browser?
|
88
|
+
version_guarded_browser? && version_below_minimum_required?
|
89
|
+
end
|
90
|
+
|
91
|
+
def version_guarded_browser?
|
92
|
+
minimum_browser_version_for_browser != nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def version_below_minimum_required?
|
96
|
+
if minimum_browser_version_for_browser
|
97
|
+
parsed_user_agent.version < UserAgent::Version.new(minimum_browser_version_for_browser.to_s)
|
98
|
+
else
|
99
|
+
true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def minimum_browser_version_for_browser
|
104
|
+
expanded_versions[normalized_browser_name]
|
105
|
+
end
|
106
|
+
|
107
|
+
def expanded_versions
|
108
|
+
@expanded_versions ||= (SETS[versions] || versions).with_indifferent_access
|
109
|
+
end
|
110
|
+
|
111
|
+
def normalized_browser_name
|
112
|
+
case name = parsed_user_agent.browser.downcase
|
113
|
+
when "internet explorer" then "ie"
|
114
|
+
else name
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionController
|
6
|
+
module BasicImplicitRender # :nodoc:
|
7
|
+
def send_action(method, *args)
|
8
|
+
ret = super
|
9
|
+
default_render unless performed?
|
10
|
+
ret
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_render
|
14
|
+
head :no_content
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|