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
data/lib/action_controller.rb
CHANGED
@@ -1,34 +1,55 @@
|
|
1
|
-
|
2
|
-
require 'abstract_controller'
|
3
|
-
require 'action_dispatch'
|
4
|
-
require 'action_controller/metal/live'
|
5
|
-
require 'action_controller/metal/strong_parameters'
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "abstract_controller"
|
6
|
+
require "action_dispatch"
|
7
|
+
require "action_controller/deprecator"
|
8
|
+
require "action_controller/metal/strong_parameters"
|
9
|
+
require "action_controller/metal/exceptions"
|
10
|
+
|
11
|
+
# # Action Controller
|
12
|
+
#
|
13
|
+
# Action Controller is a module of Action Pack.
|
14
|
+
#
|
15
|
+
# Action Controller provides a base controller class that can be subclassed to
|
16
|
+
# implement filters and actions to handle requests. The result of an action is
|
17
|
+
# typically content generated from views.
|
7
18
|
module ActionController
|
8
19
|
extend ActiveSupport::Autoload
|
9
20
|
|
21
|
+
autoload :API
|
10
22
|
autoload :Base
|
11
|
-
autoload :Caching
|
12
23
|
autoload :Metal
|
13
|
-
autoload :
|
24
|
+
autoload :Renderer
|
25
|
+
autoload :FormBuilder
|
26
|
+
|
27
|
+
eager_autoload do
|
28
|
+
autoload :Caching
|
29
|
+
end
|
14
30
|
|
15
31
|
autoload_under "metal" do
|
16
|
-
autoload :
|
32
|
+
autoload :AllowBrowser
|
17
33
|
autoload :ConditionalGet
|
34
|
+
autoload :ContentSecurityPolicy
|
18
35
|
autoload :Cookies
|
19
36
|
autoload :DataStreaming
|
37
|
+
autoload :DefaultHeaders
|
20
38
|
autoload :EtagWithTemplateDigest
|
39
|
+
autoload :EtagWithFlash
|
40
|
+
autoload :PermissionsPolicy
|
21
41
|
autoload :Flash
|
22
|
-
autoload :ForceSSL
|
23
42
|
autoload :Head
|
24
43
|
autoload :Helpers
|
25
|
-
autoload :HideActions
|
26
44
|
autoload :HttpAuthentication
|
45
|
+
autoload :BasicImplicitRender
|
27
46
|
autoload :ImplicitRender
|
28
47
|
autoload :Instrumentation
|
48
|
+
autoload :Live
|
49
|
+
autoload :Logging
|
29
50
|
autoload :MimeResponds
|
30
51
|
autoload :ParamsWrapper
|
31
|
-
autoload :
|
52
|
+
autoload :RateLimiting
|
32
53
|
autoload :Redirecting
|
33
54
|
autoload :Renderers
|
34
55
|
autoload :Rendering
|
@@ -36,23 +57,24 @@ module ActionController
|
|
36
57
|
autoload :Rescue
|
37
58
|
autoload :Streaming
|
38
59
|
autoload :StrongParameters
|
60
|
+
autoload :ParameterEncoding
|
39
61
|
autoload :Testing
|
40
62
|
autoload :UrlFor
|
41
63
|
end
|
42
64
|
|
43
|
-
|
44
|
-
|
65
|
+
autoload_under "api" do
|
66
|
+
autoload :ApiRendering
|
67
|
+
end
|
45
68
|
|
46
|
-
|
47
|
-
|
48
|
-
|
69
|
+
autoload_at "action_controller/test_case" do
|
70
|
+
autoload :TestCase
|
71
|
+
autoload :TestRequest
|
72
|
+
autoload :TemplateAssertions
|
49
73
|
end
|
50
74
|
end
|
51
75
|
|
52
76
|
# Common Active Support usage in Action Controller
|
53
|
-
require
|
54
|
-
require
|
55
|
-
require
|
56
|
-
require
|
57
|
-
require 'active_support/core_ext/uri'
|
58
|
-
require 'active_support/inflector'
|
77
|
+
require "active_support/core_ext/module/attribute_accessors"
|
78
|
+
require "active_support/core_ext/module/attr_internal"
|
79
|
+
require "active_support/core_ext/name_error"
|
80
|
+
require "active_support/inflector"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "rack/version"
|
6
|
+
|
7
|
+
module ActionDispatch
|
8
|
+
module Constants
|
9
|
+
# Response Header keys for Rack 2.x and 3.x
|
10
|
+
if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
|
11
|
+
VARY = "Vary"
|
12
|
+
CONTENT_ENCODING = "Content-Encoding"
|
13
|
+
CONTENT_SECURITY_POLICY = "Content-Security-Policy"
|
14
|
+
CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
|
15
|
+
LOCATION = "Location"
|
16
|
+
FEATURE_POLICY = "Feature-Policy"
|
17
|
+
X_REQUEST_ID = "X-Request-Id"
|
18
|
+
X_CASCADE = "X-Cascade"
|
19
|
+
SERVER_TIMING = "Server-Timing"
|
20
|
+
STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"
|
21
|
+
else
|
22
|
+
VARY = "vary"
|
23
|
+
CONTENT_ENCODING = "content-encoding"
|
24
|
+
CONTENT_SECURITY_POLICY = "content-security-policy"
|
25
|
+
CONTENT_SECURITY_POLICY_REPORT_ONLY = "content-security-policy-report-only"
|
26
|
+
LOCATION = "location"
|
27
|
+
FEATURE_POLICY = "feature-policy"
|
28
|
+
X_REQUEST_ID = "x-request-id"
|
29
|
+
X_CASCADE = "x-cascade"
|
30
|
+
SERVER_TIMING = "server-timing"
|
31
|
+
STRICT_TRANSPORT_SECURITY = "strict-transport-security"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,26 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
1
4
|
|
2
5
|
module ActionDispatch
|
3
6
|
module Http
|
4
7
|
module Cache
|
5
8
|
module Request
|
6
|
-
|
7
|
-
|
8
|
-
HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
|
9
|
+
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE"
|
10
|
+
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH"
|
9
11
|
|
10
12
|
def if_modified_since
|
11
|
-
if since =
|
13
|
+
if since = get_header(HTTP_IF_MODIFIED_SINCE)
|
12
14
|
Time.rfc2822(since) rescue nil
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
18
|
def if_none_match
|
17
|
-
|
19
|
+
get_header HTTP_IF_NONE_MATCH
|
18
20
|
end
|
19
21
|
|
20
22
|
def if_none_match_etags
|
21
|
-
|
22
|
-
etag.gsub(/^\"|\"$/, "")
|
23
|
-
end
|
23
|
+
if_none_match ? if_none_match.split(",").each(&:strip!) : []
|
24
24
|
end
|
25
25
|
|
26
26
|
def not_modified?(modified_at)
|
@@ -29,13 +29,13 @@ module ActionDispatch
|
|
29
29
|
|
30
30
|
def etag_matches?(etag)
|
31
31
|
if etag
|
32
|
-
|
33
|
-
|
32
|
+
validators = if_none_match_etags
|
33
|
+
validators.include?(etag) || validators.include?("*")
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
# Check response freshness (Last-Modified and ETag) against request
|
38
|
-
# If-Modified-Since and If-None-Match conditions. If both headers are
|
37
|
+
# Check response freshness (`Last-Modified` and ETag) against request
|
38
|
+
# `If-Modified-Since` and `If-None-Match` conditions. If both headers are
|
39
39
|
# supplied, both must match, or the request is not considered fresh.
|
40
40
|
def fresh?(response)
|
41
41
|
last_modified = if_modified_since
|
@@ -51,67 +51,107 @@ module ActionDispatch
|
|
51
51
|
end
|
52
52
|
|
53
53
|
module Response
|
54
|
-
attr_reader :cache_control
|
55
|
-
alias :etag? :etag
|
54
|
+
attr_reader :cache_control
|
56
55
|
|
57
56
|
def last_modified
|
58
|
-
if last =
|
57
|
+
if last = get_header(LAST_MODIFIED)
|
59
58
|
Time.httpdate(last)
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
63
62
|
def last_modified?
|
64
|
-
|
63
|
+
has_header? LAST_MODIFIED
|
65
64
|
end
|
66
65
|
|
67
66
|
def last_modified=(utc_time)
|
68
|
-
|
67
|
+
set_header LAST_MODIFIED, utc_time.httpdate
|
69
68
|
end
|
70
69
|
|
71
70
|
def date
|
72
|
-
if date_header =
|
71
|
+
if date_header = get_header(DATE)
|
73
72
|
Time.httpdate(date_header)
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
77
76
|
def date?
|
78
|
-
|
77
|
+
has_header? DATE
|
79
78
|
end
|
80
79
|
|
81
80
|
def date=(utc_time)
|
82
|
-
|
81
|
+
set_header DATE, utc_time.httpdate
|
82
|
+
end
|
83
|
+
|
84
|
+
# This method sets a weak ETag validator on the response so browsers and proxies
|
85
|
+
# may cache the response, keyed on the ETag. On subsequent requests, the
|
86
|
+
# `If-None-Match` header is set to the cached ETag. If it matches the current
|
87
|
+
# ETag, we can return a `304 Not Modified` response with no body, letting the
|
88
|
+
# browser or proxy know that their cache is current. Big savings in request time
|
89
|
+
# and network bandwidth.
|
90
|
+
#
|
91
|
+
# Weak ETags are considered to be semantically equivalent but not byte-for-byte
|
92
|
+
# identical. This is perfect for browser caching of HTML pages where we don't
|
93
|
+
# care about exact equality, just what the user is viewing.
|
94
|
+
#
|
95
|
+
# Strong ETags are considered byte-for-byte identical. They allow a browser or
|
96
|
+
# proxy cache to support `Range` requests, useful for paging through a PDF file
|
97
|
+
# or scrubbing through a video. Some CDNs only support strong ETags and will
|
98
|
+
# ignore weak ETags entirely.
|
99
|
+
#
|
100
|
+
# Weak ETags are what we almost always need, so they're the default. Check out
|
101
|
+
# #strong_etag= to provide a strong ETag validator.
|
102
|
+
def etag=(weak_validators)
|
103
|
+
self.weak_etag = weak_validators
|
104
|
+
end
|
105
|
+
|
106
|
+
def weak_etag=(weak_validators)
|
107
|
+
set_header "ETag", generate_weak_etag(weak_validators)
|
83
108
|
end
|
84
109
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
110
|
+
def strong_etag=(strong_validators)
|
111
|
+
set_header "ETag", generate_strong_etag(strong_validators)
|
112
|
+
end
|
113
|
+
|
114
|
+
def etag?; etag; end
|
115
|
+
|
116
|
+
# True if an ETag is set, and it's a weak validator (preceded with `W/`).
|
117
|
+
def weak_etag?
|
118
|
+
etag? && etag.start_with?('W/"')
|
119
|
+
end
|
120
|
+
|
121
|
+
# True if an ETag is set, and it isn't a weak validator (not preceded with
|
122
|
+
# `W/`).
|
123
|
+
def strong_etag?
|
124
|
+
etag? && !weak_etag?
|
88
125
|
end
|
89
126
|
|
90
127
|
private
|
128
|
+
DATE = "Date"
|
129
|
+
LAST_MODIFIED = "Last-Modified"
|
130
|
+
SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate])
|
91
131
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
132
|
+
def generate_weak_etag(validators)
|
133
|
+
"W/#{generate_strong_etag(validators)}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def generate_strong_etag(validators)
|
137
|
+
%("#{ActiveSupport::Digest.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
|
138
|
+
end
|
97
139
|
|
98
140
|
def cache_control_segments
|
99
|
-
if cache_control =
|
100
|
-
cache_control.delete(
|
101
|
-
else
|
102
|
-
[]
|
141
|
+
if cache_control = _cache_control
|
142
|
+
cache_control.delete(" ").split(",")
|
103
143
|
end
|
104
144
|
end
|
105
145
|
|
106
146
|
def cache_control_headers
|
107
147
|
cache_control = {}
|
108
148
|
|
109
|
-
cache_control_segments
|
110
|
-
directive, argument = segment.split(
|
149
|
+
cache_control_segments&.each do |segment|
|
150
|
+
directive, argument = segment.split("=", 2)
|
111
151
|
|
112
152
|
if SPECIAL_KEYS.include? directive
|
113
|
-
|
114
|
-
cache_control[
|
153
|
+
directive.tr!("-", "_")
|
154
|
+
cache_control[directive.to_sym] = argument || true
|
115
155
|
else
|
116
156
|
cache_control[:extras] ||= []
|
117
157
|
cache_control[:extras] << segment
|
@@ -123,52 +163,68 @@ module ActionDispatch
|
|
123
163
|
|
124
164
|
def prepare_cache_control!
|
125
165
|
@cache_control = cache_control_headers
|
126
|
-
@etag = self[ETAG]
|
127
166
|
end
|
128
167
|
|
168
|
+
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
169
|
+
NO_STORE = "no-store"
|
170
|
+
NO_CACHE = "no-cache"
|
171
|
+
PUBLIC = "public"
|
172
|
+
PRIVATE = "private"
|
173
|
+
MUST_REVALIDATE = "must-revalidate"
|
174
|
+
|
129
175
|
def handle_conditional_get!
|
130
|
-
|
131
|
-
|
176
|
+
# Normally default cache control setting is handled by ETag middleware. But, if
|
177
|
+
# an etag is already set, the middleware defaults to `no-cache` unless a default
|
178
|
+
# `Cache-Control` value is previously set. So, set a default one here.
|
179
|
+
if (etag? || last_modified?) && !self._cache_control
|
180
|
+
self._cache_control = DEFAULT_CACHE_CONTROL
|
132
181
|
end
|
133
182
|
end
|
134
183
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
184
|
+
def merge_and_normalize_cache_control!(cache_control)
|
185
|
+
control = cache_control_headers
|
186
|
+
|
187
|
+
return if control.empty? && cache_control.empty? # Let middleware handle default behavior
|
188
|
+
|
189
|
+
if cache_control.any?
|
190
|
+
# Any caching directive coming from a controller overrides no-cache/no-store in
|
191
|
+
# the default Cache-Control header.
|
192
|
+
control.delete(:no_cache)
|
193
|
+
control.delete(:no_store)
|
140
194
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
195
|
+
if extras = control.delete(:extras)
|
196
|
+
cache_control[:extras] ||= []
|
197
|
+
cache_control[:extras] += extras
|
198
|
+
cache_control[:extras].uniq!
|
199
|
+
end
|
200
|
+
|
201
|
+
control.merge! cache_control
|
148
202
|
end
|
149
203
|
|
150
|
-
|
151
|
-
control.merge! @cache_control
|
204
|
+
options = []
|
152
205
|
|
153
|
-
if control
|
154
|
-
|
206
|
+
if control[:no_store]
|
207
|
+
options << PRIVATE if control[:private]
|
208
|
+
options << NO_STORE
|
155
209
|
elsif control[:no_cache]
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
end
|
210
|
+
options << PUBLIC if control[:public]
|
211
|
+
options << NO_CACHE
|
212
|
+
options.concat(control[:extras]) if control[:extras]
|
160
213
|
else
|
161
|
-
extras
|
214
|
+
extras = control[:extras]
|
162
215
|
max_age = control[:max_age]
|
216
|
+
stale_while_revalidate = control[:stale_while_revalidate]
|
217
|
+
stale_if_error = control[:stale_if_error]
|
163
218
|
|
164
|
-
options = []
|
165
219
|
options << "max-age=#{max_age.to_i}" if max_age
|
166
220
|
options << (control[:public] ? PUBLIC : PRIVATE)
|
167
221
|
options << MUST_REVALIDATE if control[:must_revalidate]
|
222
|
+
options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
|
223
|
+
options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
|
168
224
|
options.concat(extras) if extras
|
169
|
-
|
170
|
-
headers[CACHE_CONTROL] = options.join(", ")
|
171
225
|
end
|
226
|
+
|
227
|
+
self._cache_control = options.join(", ")
|
172
228
|
end
|
173
229
|
end
|
174
230
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module Http
|
7
|
+
class ContentDisposition # :nodoc:
|
8
|
+
def self.format(disposition:, filename:)
|
9
|
+
new(disposition: disposition, filename: filename).to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :disposition, :filename
|
13
|
+
|
14
|
+
def initialize(disposition:, filename:)
|
15
|
+
@disposition = disposition
|
16
|
+
@filename = filename
|
17
|
+
end
|
18
|
+
|
19
|
+
TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!\#$+.^_`|~-]/
|
20
|
+
|
21
|
+
def ascii_filename
|
22
|
+
'filename="' + percent_escape(I18n.transliterate(filename), TRADITIONAL_ESCAPED_CHAR) + '"'
|
23
|
+
end
|
24
|
+
|
25
|
+
RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!\#$&+.^_`|~-]/
|
26
|
+
|
27
|
+
def utf8_filename
|
28
|
+
"filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
if filename
|
33
|
+
"#{disposition}; #{ascii_filename}; #{utf8_filename}"
|
34
|
+
else
|
35
|
+
"#{disposition}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def percent_escape(string, pattern)
|
41
|
+
string.gsub(pattern) do |char|
|
42
|
+
char.bytes.map { |byte| "%%%02X" % byte }.join
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|