actionpack 5.0.0.beta3 → 5.0.0.beta4
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 +4 -4
- data/CHANGELOG.md +101 -16
- data/lib/abstract_controller/base.rb +2 -4
- data/lib/abstract_controller/error.rb +4 -0
- data/lib/abstract_controller/helpers.rb +2 -1
- data/lib/abstract_controller/rendering.rb +1 -0
- data/lib/action_controller/api.rb +20 -19
- data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
- data/lib/action_controller/metal/conditional_get.rb +52 -21
- data/lib/action_controller/metal/cookies.rb +1 -1
- data/lib/action_controller/metal/data_streaming.rb +9 -10
- data/lib/action_controller/metal/force_ssl.rb +4 -4
- data/lib/action_controller/metal/http_authentication.rb +8 -3
- data/lib/action_controller/metal/implicit_render.rb +55 -17
- data/lib/action_controller/metal/instrumentation.rb +3 -2
- data/lib/action_controller/metal/live.rb +2 -2
- data/lib/action_controller/metal/mime_responds.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +1 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +3 -2
- data/lib/action_controller/metal/rescue.rb +6 -2
- data/lib/action_controller/metal/strong_parameters.rb +30 -3
- data/lib/action_controller/renderer.rb +1 -1
- data/lib/action_controller/test_case.rb +2 -2
- data/lib/action_dispatch.rb +1 -1
- data/lib/action_dispatch/http/cache.rb +49 -15
- data/lib/action_dispatch/http/filter_parameters.rb +9 -3
- data/lib/action_dispatch/http/headers.rb +2 -2
- data/lib/action_dispatch/http/mime_types.rb +1 -1
- data/lib/action_dispatch/http/request.rb +0 -1
- data/lib/action_dispatch/journey/formatter.rb +7 -2
- data/lib/action_dispatch/journey/route.rb +1 -1
- data/lib/action_dispatch/middleware/callbacks.rb +10 -1
- data/lib/action_dispatch/middleware/exception_wrapper.rb +0 -1
- data/lib/action_dispatch/middleware/executor.rb +19 -0
- data/lib/action_dispatch/middleware/flash.rb +5 -0
- data/lib/action_dispatch/middleware/params_parser.rb +1 -0
- data/lib/action_dispatch/middleware/reloader.rb +12 -54
- data/lib/action_dispatch/middleware/ssl.rb +19 -3
- data/lib/action_dispatch/railtie.rb +2 -0
- data/lib/action_dispatch/request/session.rb +16 -10
- data/lib/action_dispatch/routing.rb +12 -3
- data/lib/action_dispatch/routing/inspector.rb +3 -3
- data/lib/action_dispatch/routing/mapper.rb +6 -3
- data/lib/action_dispatch/routing/route_set.rb +16 -15
- data/lib/action_dispatch/routing/url_for.rb +1 -1
- data/lib/action_dispatch/testing/assertions/routing.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +43 -27
- data/lib/action_pack/gem_version.rb +1 -1
- metadata +12 -11
- data/lib/action_dispatch/middleware/load_interlock.rb +0 -21
@@ -25,14 +25,13 @@ module ActionController #:nodoc:
|
|
25
25
|
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
26
26
|
# Defaults to <tt>File.basename(path)</tt>.
|
27
27
|
# * <tt>:type</tt> - specifies an HTTP content type.
|
28
|
-
# You can specify either a string or a symbol for a registered type register
|
29
|
-
# <tt
|
30
|
-
# If
|
31
|
-
# If no content type is registered for the extension, default type 'application/octet-stream' will be used.
|
28
|
+
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
|
29
|
+
# If omitted, the type will be inferred from the file extension specified in <tt>:filename</tt>.
|
30
|
+
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
|
32
31
|
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
33
32
|
# Valid values are 'inline' and 'attachment' (default).
|
34
33
|
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
|
35
|
-
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
|
34
|
+
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser to guess the filename from
|
36
35
|
# the URL, which is necessary for i18n filenames on certain browsers
|
37
36
|
# (setting <tt>:filename</tt> overrides this option).
|
38
37
|
#
|
@@ -79,14 +78,14 @@ module ActionController #:nodoc:
|
|
79
78
|
# <tt>render plain: data</tt>, but also allows you to specify whether
|
80
79
|
# the browser should display the response as a file attachment (i.e. in a
|
81
80
|
# download dialog) or as inline data. You may also set the content type,
|
82
|
-
# the
|
81
|
+
# the file name, and other things.
|
83
82
|
#
|
84
83
|
# Options:
|
85
84
|
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
86
|
-
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
87
|
-
# either a string or a symbol for a registered type
|
88
|
-
# If omitted, type will be
|
89
|
-
# If no content type is registered for the extension, default type 'application/octet-stream' will be used.
|
85
|
+
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
86
|
+
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
|
87
|
+
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
88
|
+
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
|
90
89
|
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
91
90
|
# Valid values are 'inline' and 'attachment' (default).
|
92
91
|
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
|
@@ -2,17 +2,17 @@ require 'active_support/core_ext/hash/except'
|
|
2
2
|
require 'active_support/core_ext/hash/slice'
|
3
3
|
|
4
4
|
module ActionController
|
5
|
-
# This module provides a method which will redirect browser to use HTTPS
|
5
|
+
# This module provides a method which will redirect the browser to use HTTPS
|
6
6
|
# protocol. This will ensure that user's sensitive information will be
|
7
|
-
# transferred safely over the internet. You _should_ always force browser
|
7
|
+
# transferred safely over the internet. You _should_ always force the browser
|
8
8
|
# to use HTTPS when you're transferring sensitive information such as
|
9
9
|
# user authentication, account information, or credit card information.
|
10
10
|
#
|
11
11
|
# Note that if you are really concerned about your application security,
|
12
12
|
# you might consider using +config.force_ssl+ in your config file instead.
|
13
13
|
# That will ensure all the data transferred via HTTPS protocol and prevent
|
14
|
-
# user from getting session hijacked when accessing the site
|
15
|
-
# HTTP protocol.
|
14
|
+
# the user from getting their session hijacked when accessing the site over
|
15
|
+
# unsecured HTTP protocol.
|
16
16
|
module ForceSSL
|
17
17
|
extend ActiveSupport::Concern
|
18
18
|
include AbstractController::Callbacks
|
@@ -310,9 +310,9 @@ module ActionController
|
|
310
310
|
end
|
311
311
|
|
312
312
|
# Might want a shorter timeout depending on whether the request
|
313
|
-
# is a PATCH, PUT, or POST, and if client is browser or web service.
|
313
|
+
# is a PATCH, PUT, or POST, and if the client is a browser or web service.
|
314
314
|
# Can be much shorter if the Stale directive is implemented. This would
|
315
|
-
# allow a user to use new nonce without prompting user again for their
|
315
|
+
# allow a user to use new nonce without prompting the user again for their
|
316
316
|
# username and password.
|
317
317
|
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
|
318
318
|
return false if value.nil?
|
@@ -347,7 +347,12 @@ module ActionController
|
|
347
347
|
# private
|
348
348
|
# def authenticate
|
349
349
|
# authenticate_or_request_with_http_token do |token, options|
|
350
|
-
#
|
350
|
+
# # Compare the tokens in a time-constant manner, to mitigate
|
351
|
+
# # timing attacks.
|
352
|
+
# ActiveSupport::SecurityUtils.secure_compare(
|
353
|
+
# ::Digest::SHA256.hexdigest(token),
|
354
|
+
# ::Digest::SHA256.hexdigest(TOKEN)
|
355
|
+
# )
|
351
356
|
# end
|
352
357
|
# end
|
353
358
|
# end
|
@@ -1,29 +1,62 @@
|
|
1
|
+
require 'active_support/core_ext/string/strip'
|
2
|
+
|
1
3
|
module ActionController
|
4
|
+
# Handles implicit rendering for a controller action that does not
|
5
|
+
# explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
|
6
|
+
#
|
7
|
+
# For API controllers, the implicit response is always 204 No Content.
|
8
|
+
#
|
9
|
+
# For all other controllers, we use these heuristics to decide whether to
|
10
|
+
# render a template, raise an error for a missing template, or respond with
|
11
|
+
# 204 No Content:
|
12
|
+
#
|
13
|
+
# First, if we DO find a template, it's rendered. Template lookup accounts
|
14
|
+
# for the action name, locales, format, variant, template handlers, and more
|
15
|
+
# (see +render+ for details).
|
16
|
+
#
|
17
|
+
# Second, if we DON'T find a template but the controller action does have
|
18
|
+
# templates for other formats, variants, etc., then we trust that you meant
|
19
|
+
# to provide a template for this response, too, and we raise
|
20
|
+
# <tt>ActionController::UnknownFormat</tt> with an explanation.
|
21
|
+
#
|
22
|
+
# Third, if we DON'T find a template AND the request is a page load in a web
|
23
|
+
# browser (technically, a non-XHR GET request for an HTML response) where
|
24
|
+
# you reasonably expect to have rendered a template, then we raise
|
25
|
+
# <tt>ActionView::UnknownFormat</tt> with an explanation.
|
26
|
+
#
|
27
|
+
# Finally, if we DON'T find a template AND the request isn't a browser page
|
28
|
+
# load, then we implicitly respond with 204 No Content.
|
2
29
|
module ImplicitRender
|
3
30
|
|
31
|
+
# :stopdoc:
|
4
32
|
include BasicImplicitRender
|
5
33
|
|
6
|
-
# Renders the template corresponding to the controller action, if it exists.
|
7
|
-
# The action name, format, and variant are all taken into account.
|
8
|
-
# For example, the "new" action with an HTML format and variant "phone"
|
9
|
-
# would try to render the <tt>new.html+phone.erb</tt> template.
|
10
|
-
#
|
11
|
-
# If no template is found <tt>ActionController::BasicImplicitRender</tt>'s implementation is called, unless
|
12
|
-
# a block is passed. In that case, it will override the super implementation.
|
13
|
-
#
|
14
|
-
# default_render do
|
15
|
-
# head 404 # No template was found
|
16
|
-
# end
|
17
34
|
def default_render(*args)
|
18
35
|
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
|
19
36
|
render(*args)
|
37
|
+
elsif any_templates?(action_name.to_s, _prefixes)
|
38
|
+
message = "#{self.class.name}\##{action_name} is missing a template " \
|
39
|
+
"for this request format and variant.\n" \
|
40
|
+
"\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \
|
41
|
+
"\nrequest.variant: #{request.variant.inspect}"
|
42
|
+
|
43
|
+
raise ActionController::UnknownFormat, message
|
44
|
+
elsif interactive_browser_request?
|
45
|
+
message = "#{self.class.name}\##{action_name} is missing a template " \
|
46
|
+
"for this request format and variant.\n\n" \
|
47
|
+
"request.formats: #{request.formats.map(&:to_s).inspect}\n" \
|
48
|
+
"request.variant: #{request.variant.inspect}\n\n" \
|
49
|
+
"NOTE! For XHR/Ajax or API requests, this action would normally " \
|
50
|
+
"respond with 204 No Content: an empty white screen. Since you're " \
|
51
|
+
"loading it in a web browser, we assume that you expected to " \
|
52
|
+
"actually render a template, not… nothing, so we're showing an " \
|
53
|
+
"error to be extra-clear. If you expect 204 No Content, carry on. " \
|
54
|
+
"That's what you'll get from an XHR or API request. Give it a shot."
|
55
|
+
|
56
|
+
raise ActionController::UnknownFormat, message
|
20
57
|
else
|
21
|
-
if
|
22
|
-
|
23
|
-
else
|
24
|
-
logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
|
25
|
-
super
|
26
|
-
end
|
58
|
+
logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
|
59
|
+
super
|
27
60
|
end
|
28
61
|
end
|
29
62
|
|
@@ -32,5 +65,10 @@ module ActionController
|
|
32
65
|
"default_render"
|
33
66
|
end
|
34
67
|
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def interactive_browser_request?
|
71
|
+
request.get? && request.format == Mime[:html] && !request.xhr?
|
72
|
+
end
|
35
73
|
end
|
36
74
|
end
|
@@ -19,6 +19,7 @@ module ActionController
|
|
19
19
|
:controller => self.class.name,
|
20
20
|
:action => self.action_name,
|
21
21
|
:params => request.filtered_parameters,
|
22
|
+
:headers => request.headers,
|
22
23
|
:format => request.format.ref,
|
23
24
|
:method => request.request_method,
|
24
25
|
:path => request.fullpath
|
@@ -74,8 +75,8 @@ module ActionController
|
|
74
75
|
ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
|
75
76
|
end
|
76
77
|
|
77
|
-
# A hook which allows you to clean up any time taken into account in
|
78
|
-
# views
|
78
|
+
# A hook which allows you to clean up any time, wrongly taken into account in
|
79
|
+
# views, like database querying time.
|
79
80
|
#
|
80
81
|
# def cleanup_view_runtime
|
81
82
|
# super - time_taken_in_something_expensive
|
@@ -3,7 +3,7 @@ require 'delegate'
|
|
3
3
|
require 'active_support/json'
|
4
4
|
|
5
5
|
module ActionController
|
6
|
-
# Mix this module
|
6
|
+
# Mix this module into your controller, and all actions in that controller
|
7
7
|
# will be able to stream data to the client as it's written.
|
8
8
|
#
|
9
9
|
# class MyController < ActionController::Base
|
@@ -20,7 +20,7 @@ module ActionController
|
|
20
20
|
# end
|
21
21
|
# end
|
22
22
|
#
|
23
|
-
# There are a few caveats with this
|
23
|
+
# There are a few caveats with this module. You *cannot* write headers after the
|
24
24
|
# response has been committed (Response#committed? will return truthy).
|
25
25
|
# Calling +write+ or +close+ on the response stream will cause the response
|
26
26
|
# object to be committed. Make sure all headers are set before calling write
|
@@ -198,7 +198,7 @@ module ActionController #:nodoc:
|
|
198
198
|
_process_format(format)
|
199
199
|
_set_rendered_content_type format
|
200
200
|
response = collector.response
|
201
|
-
response
|
201
|
+
response.call if response
|
202
202
|
else
|
203
203
|
raise ActionController::UnknownFormat
|
204
204
|
end
|
@@ -84,7 +84,7 @@ module ActionController
|
|
84
84
|
# redirect_back fallback_location: proc { edit_post_url(@post) }
|
85
85
|
#
|
86
86
|
# All options that can be passed to <tt>redirect_to</tt> are accepted as
|
87
|
-
# options and the behavior is
|
87
|
+
# options and the behavior is identical.
|
88
88
|
def redirect_back(fallback_location:, **args)
|
89
89
|
if referer = request.headers["Referer"]
|
90
90
|
redirect_to referer, **args
|
@@ -213,7 +213,7 @@ module ActionController #:nodoc:
|
|
213
213
|
|
214
214
|
if !verified_request?
|
215
215
|
if logger && log_warning_on_csrf_failure
|
216
|
-
logger.warn "Can't verify CSRF token authenticity"
|
216
|
+
logger.warn "Can't verify CSRF token authenticity."
|
217
217
|
end
|
218
218
|
handle_unverified_request
|
219
219
|
end
|
@@ -405,7 +405,8 @@ module ActionController #:nodoc:
|
|
405
405
|
end
|
406
406
|
|
407
407
|
def normalize_action_path(action_path)
|
408
|
-
|
408
|
+
uri = URI.parse(action_path)
|
409
|
+
uri.path.chomp('/')
|
409
410
|
end
|
410
411
|
end
|
411
412
|
end
|
@@ -7,8 +7,12 @@ module ActionController #:nodoc:
|
|
7
7
|
include ActiveSupport::Rescuable
|
8
8
|
|
9
9
|
def rescue_with_handler(exception)
|
10
|
-
if exception.cause
|
11
|
-
|
10
|
+
if exception.cause
|
11
|
+
handler_index = index_of_handler_for_rescue(exception) || Float::INFINITY
|
12
|
+
cause_handler_index = index_of_handler_for_rescue(exception.cause)
|
13
|
+
if cause_handler_index && cause_handler_index <= handler_index
|
14
|
+
exception = exception.cause
|
15
|
+
end
|
12
16
|
end
|
13
17
|
super(exception)
|
14
18
|
end
|
@@ -184,6 +184,13 @@ module ActionController
|
|
184
184
|
# Returns an unsafe, unfiltered
|
185
185
|
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
|
186
186
|
# parameter.
|
187
|
+
#
|
188
|
+
# params = ActionController::Parameters.new({
|
189
|
+
# name: 'Senjougahara Hitagi',
|
190
|
+
# oddity: 'Heavy stone crab'
|
191
|
+
# })
|
192
|
+
# params.to_unsafe_h
|
193
|
+
# # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"}
|
187
194
|
def to_unsafe_h
|
188
195
|
convert_parameters_to_hashes(@parameters, :to_unsafe_h)
|
189
196
|
end
|
@@ -430,6 +437,21 @@ module ActionController
|
|
430
437
|
)
|
431
438
|
end
|
432
439
|
|
440
|
+
if Hash.method_defined?(:dig)
|
441
|
+
# Extracts the nested parameter from the given +keys+ by calling +dig+
|
442
|
+
# at each step. Returns +nil+ if any intermediate step is +nil+.
|
443
|
+
#
|
444
|
+
# params = ActionController::Parameters.new(foo: { bar: { baz: 1 } })
|
445
|
+
# params.dig(:foo, :bar, :baz) # => 1
|
446
|
+
# params.dig(:foo, :zot, :xyz) # => nil
|
447
|
+
#
|
448
|
+
# params2 = ActionController::Parameters.new(foo: [10, 11, 12])
|
449
|
+
# params2.dig(:foo, 1) # => 11
|
450
|
+
def dig(*keys)
|
451
|
+
convert_value_to_parameters(@parameters.dig(*keys))
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
433
455
|
# Returns a new <tt>ActionController::Parameters</tt> instance that
|
434
456
|
# includes only the given +keys+. If the given +keys+
|
435
457
|
# don't exist, returns an empty hash.
|
@@ -734,6 +756,10 @@ module ActionController
|
|
734
756
|
end
|
735
757
|
end
|
736
758
|
|
759
|
+
def non_scalar?(value)
|
760
|
+
value.is_a?(Array) || value.is_a?(Parameters)
|
761
|
+
end
|
762
|
+
|
737
763
|
EMPTY_ARRAY = []
|
738
764
|
def hash_filter(params, filter)
|
739
765
|
filter = filter.with_indifferent_access
|
@@ -748,7 +774,7 @@ module ActionController
|
|
748
774
|
array_of_permitted_scalars?(self[key]) do |val|
|
749
775
|
params[key] = val
|
750
776
|
end
|
751
|
-
|
777
|
+
elsif non_scalar?(value)
|
752
778
|
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
|
753
779
|
params[key] = each_element(value) do |element|
|
754
780
|
element.permit(*Array.wrap(filter[key]))
|
@@ -799,7 +825,8 @@ module ActionController
|
|
799
825
|
# end
|
800
826
|
#
|
801
827
|
# In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
|
802
|
-
# will need to specify which nested attributes should be whitelisted.
|
828
|
+
# will need to specify which nested attributes should be whitelisted. You might want
|
829
|
+
# to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information.
|
803
830
|
#
|
804
831
|
# class Person
|
805
832
|
# has_many :pets
|
@@ -819,7 +846,7 @@ module ActionController
|
|
819
846
|
# # It's mandatory to specify the nested attributes that should be whitelisted.
|
820
847
|
# # If you use `permit` with just the key that points to the nested attributes hash,
|
821
848
|
# # it will return an empty hash.
|
822
|
-
# params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
|
849
|
+
# params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
|
823
850
|
# end
|
824
851
|
# end
|
825
852
|
#
|
@@ -45,7 +45,7 @@ module ActionController
|
|
45
45
|
}.freeze
|
46
46
|
|
47
47
|
# Create a new renderer instance for a specific controller class.
|
48
|
-
def self.for(controller, env = {}, defaults = DEFAULTS)
|
48
|
+
def self.for(controller, env = {}, defaults = DEFAULTS.dup)
|
49
49
|
new(controller, env, defaults)
|
50
50
|
end
|
51
51
|
|
@@ -176,7 +176,7 @@ module ActionController
|
|
176
176
|
def initialize(session = {})
|
177
177
|
super(nil, nil)
|
178
178
|
@id = SecureRandom.hex(16)
|
179
|
-
@data = session
|
179
|
+
@data = stringify_keys(session)
|
180
180
|
@loaded = true
|
181
181
|
end
|
182
182
|
|
@@ -428,7 +428,7 @@ module ActionController
|
|
428
428
|
end
|
429
429
|
alias xhr :xml_http_request
|
430
430
|
|
431
|
-
# Simulate
|
431
|
+
# Simulate an HTTP request to +action+ by specifying request method,
|
432
432
|
# parameters and set/volley the response.
|
433
433
|
#
|
434
434
|
# - +action+: The controller action to call.
|
data/lib/action_dispatch.rb
CHANGED
@@ -17,9 +17,7 @@ module ActionDispatch
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def if_none_match_etags
|
20
|
-
|
21
|
-
etag.gsub(/^\"|\"$/, "")
|
22
|
-
end
|
20
|
+
if_none_match ? if_none_match.split(/\s*,\s*/) : []
|
23
21
|
end
|
24
22
|
|
25
23
|
def not_modified?(modified_at)
|
@@ -28,8 +26,8 @@ module ActionDispatch
|
|
28
26
|
|
29
27
|
def etag_matches?(etag)
|
30
28
|
if etag
|
31
|
-
|
32
|
-
|
29
|
+
validators = if_none_match_etags
|
30
|
+
validators.include?(etag) || validators.include?('*')
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
@@ -80,27 +78,63 @@ module ActionDispatch
|
|
80
78
|
set_header DATE, utc_time.httpdate
|
81
79
|
end
|
82
80
|
|
83
|
-
# This method
|
84
|
-
#
|
81
|
+
# This method sets a weak ETag validator on the response so browsers
|
82
|
+
# and proxies may cache the response, keyed on the ETag. On subsequent
|
83
|
+
# requests, the If-None-Match header is set to the cached ETag. If it
|
84
|
+
# matches the current ETag, we can return a 304 Not Modified response
|
85
|
+
# with no body, letting the browser or proxy know that their cache is
|
86
|
+
# current. Big savings in request time and network bandwidth.
|
87
|
+
#
|
88
|
+
# Weak ETags are considered to be semantically equivalent but not
|
89
|
+
# byte-for-byte identical. This is perfect for browser caching of HTML
|
90
|
+
# pages where we don't care about exact equality, just what the user
|
91
|
+
# is viewing.
|
85
92
|
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
|
92
|
-
|
93
|
-
|
93
|
+
# Strong ETags are considered byte-for-byte identical. They allow a
|
94
|
+
# browser or proxy cache to support Range requests, useful for paging
|
95
|
+
# through a PDF file or scrubbing through a video. Some CDNs only
|
96
|
+
# support strong ETags and will ignore weak ETags entirely.
|
97
|
+
#
|
98
|
+
# Weak ETags are what we almost always need, so they're the default.
|
99
|
+
# Check out `#strong_etag=` to provide a strong ETag validator.
|
100
|
+
def etag=(weak_validators)
|
101
|
+
self.weak_etag = weak_validators
|
102
|
+
end
|
103
|
+
|
104
|
+
def weak_etag=(weak_validators)
|
105
|
+
set_header 'ETag', generate_weak_etag(weak_validators)
|
106
|
+
end
|
107
|
+
|
108
|
+
def strong_etag=(strong_validators)
|
109
|
+
set_header 'ETag', generate_strong_etag(strong_validators)
|
94
110
|
end
|
95
111
|
|
96
112
|
def etag?; etag; end
|
97
113
|
|
114
|
+
# True if an ETag is set and it's a weak validator (preceded with W/)
|
115
|
+
def weak_etag?
|
116
|
+
etag? && etag.starts_with?('W/"')
|
117
|
+
end
|
118
|
+
|
119
|
+
# True if an ETag is set and it isn't a weak validator (not preceded with W/)
|
120
|
+
def strong_etag?
|
121
|
+
etag? && !weak_etag?
|
122
|
+
end
|
123
|
+
|
98
124
|
private
|
99
125
|
|
100
126
|
DATE = 'Date'.freeze
|
101
127
|
LAST_MODIFIED = "Last-Modified".freeze
|
102
128
|
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
|
103
129
|
|
130
|
+
def generate_weak_etag(validators)
|
131
|
+
"W/#{generate_strong_etag(validators)}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def generate_strong_etag(validators)
|
135
|
+
%("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
|
136
|
+
end
|
137
|
+
|
104
138
|
def cache_control_segments
|
105
139
|
if cache_control = _cache_control
|
106
140
|
cache_control.delete(' ').split(',')
|