Fixed #23755 -- Added support for multiple field names in the no-cache Cache-Control directive to patch_cache_control().

https://tools.ietf.org/html/rfc7234#section-5.2.2.2
This commit is contained in:
Flavio Curella 2019-09-26 13:58:14 -07:00 committed by Mariusz Felisiak
parent 2a6f45e08e
commit ed112fadc1
5 changed files with 43 additions and 8 deletions

View File

@ -19,6 +19,7 @@ An example: i18n middleware would need to distinguish caches by the
import hashlib import hashlib
import re import re
import time import time
from collections import defaultdict
from django.conf import settings from django.conf import settings
from django.core.cache import caches from django.core.cache import caches
@ -53,17 +54,21 @@ def patch_cache_control(response, **kwargs):
else: else:
return (t[0].lower(), True) return (t[0].lower(), True)
def dictvalue(t): def dictvalue(*t):
if t[1] is True: if t[1] is True:
return t[0] return t[0]
else: else:
return '%s=%s' % (t[0], t[1]) return '%s=%s' % (t[0], t[1])
cc = defaultdict(set)
if response.get('Cache-Control'): if response.get('Cache-Control'):
cc = cc_delim_re.split(response['Cache-Control']) for field in cc_delim_re.split(response['Cache-Control']):
cc = dict(dictitem(el) for el in cc) directive, value = dictitem(field)
else: if directive == 'no-cache':
cc = {} # no-cache supports multiple field names.
cc[directive].add(value)
else:
cc[directive] = value
# If there's already a max-age header but we're being asked to set a new # If there's already a max-age header but we're being asked to set a new
# max-age, use the minimum of the two ages. In practice this happens when # max-age, use the minimum of the two ages. In practice this happens when
@ -78,8 +83,23 @@ def patch_cache_control(response, **kwargs):
del cc['public'] del cc['public']
for (k, v) in kwargs.items(): for (k, v) in kwargs.items():
cc[k.replace('_', '-')] = v directive = k.replace('_', '-')
cc = ', '.join(dictvalue(el) for el in cc.items()) if directive == 'no-cache':
# no-cache supports multiple field names.
cc[directive].add(v)
else:
cc[directive] = v
directives = []
for directive, values in cc.items():
if isinstance(values, set):
if True in values:
# True takes precedence.
values = {True}
directives.extend([dictvalue(directive, value) for value in values])
else:
directives.append(dictvalue(directive, values))
cc = ', '.join(directives)
response['Cache-Control'] = cc response['Cache-Control'] = cc

View File

@ -43,6 +43,11 @@ need to distinguish caches by the ``Accept-language`` header.
* All other parameters are added with their value, after applying * All other parameters are added with their value, after applying
``str()`` to it. ``str()`` to it.
.. versionchanged:: 3.1
Support for multiple field names in the ``no-cache`` directive was
added.
.. function:: get_max_age(response) .. function:: get_max_age(response)
Returns the max-age from the response Cache-Control header as an integer Returns the max-age from the response Cache-Control header as an integer

View File

@ -104,7 +104,10 @@ Minor features
Cache Cache
~~~~~ ~~~~~
* ... * The :func:`~django.views.decorators.cache.cache_control` decorator and
:func:`~django.utils.cache.patch_cache_control` method now support multiple
field names in the ``no-cache`` directive for the ``Cache-Control`` header,
according to :rfc:`7234#section-5.2.2.2`.
CSRF CSRF
~~~~ ~~~~

View File

@ -1277,6 +1277,7 @@ Here are some more examples:
* ``no_transform=True`` * ``no_transform=True``
* ``must_revalidate=True`` * ``must_revalidate=True``
* ``stale_while_revalidate=num_seconds`` * ``stale_while_revalidate=num_seconds``
* ``no_cache=True``
The full list of known directives can be found in the `IANA registry`_ The full list of known directives can be found in the `IANA registry`_
(note that not all of them apply to responses). (note that not all of them apply to responses).

View File

@ -1705,6 +1705,12 @@ class CacheUtils(SimpleTestCase):
('', {'no-cache': 'Set-Cookie'}, {'no-cache=Set-Cookie'}), ('', {'no-cache': 'Set-Cookie'}, {'no-cache=Set-Cookie'}),
('no-cache=Set-Cookie', {'no_cache': True}, {'no-cache'}), ('no-cache=Set-Cookie', {'no_cache': True}, {'no-cache'}),
('no-cache=Set-Cookie,no-cache=Link', {'no_cache': True}, {'no-cache'}), ('no-cache=Set-Cookie,no-cache=Link', {'no_cache': True}, {'no-cache'}),
('no-cache=Set-Cookie', {'no_cache': 'Link'}, {'no-cache=Set-Cookie', 'no-cache=Link'}),
(
'no-cache=Set-Cookie,no-cache=Link',
{'no_cache': 'Custom'},
{'no-cache=Set-Cookie', 'no-cache=Link', 'no-cache=Custom'},
),
# Test whether private/public attributes are mutually exclusive # Test whether private/public attributes are mutually exclusive
('private', {'private': True}, {'private'}), ('private', {'private': True}, {'private'}),
('private', {'public': True}, {'public'}), ('private', {'public': True}, {'public'}),