From ed112fadc1cfa400dbee6080bf82fd7536ea4c72 Mon Sep 17 00:00:00 2001 From: Flavio Curella Date: Thu, 26 Sep 2019 13:58:14 -0700 Subject: [PATCH] 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 --- django/utils/cache.py | 34 +++++++++++++++++++++++++++------- docs/ref/utils.txt | 5 +++++ docs/releases/3.1.txt | 5 ++++- docs/topics/cache.txt | 1 + tests/cache/tests.py | 6 ++++++ 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/django/utils/cache.py b/django/utils/cache.py index 3ee01a1e946..14e8256b949 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -19,6 +19,7 @@ An example: i18n middleware would need to distinguish caches by the import hashlib import re import time +from collections import defaultdict from django.conf import settings from django.core.cache import caches @@ -53,17 +54,21 @@ def patch_cache_control(response, **kwargs): else: return (t[0].lower(), True) - def dictvalue(t): + def dictvalue(*t): if t[1] is True: return t[0] else: return '%s=%s' % (t[0], t[1]) + cc = defaultdict(set) if response.get('Cache-Control'): - cc = cc_delim_re.split(response['Cache-Control']) - cc = dict(dictitem(el) for el in cc) - else: - cc = {} + for field in cc_delim_re.split(response['Cache-Control']): + directive, value = dictitem(field) + if directive == 'no-cache': + # 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 # 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'] for (k, v) in kwargs.items(): - cc[k.replace('_', '-')] = v - cc = ', '.join(dictvalue(el) for el in cc.items()) + directive = k.replace('_', '-') + 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 diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 63db48b98ff..4f174c5efe8 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -43,6 +43,11 @@ need to distinguish caches by the ``Accept-language`` header. * All other parameters are added with their value, after applying ``str()`` to it. + .. versionchanged:: 3.1 + + Support for multiple field names in the ``no-cache`` directive was + added. + .. function:: get_max_age(response) Returns the max-age from the response Cache-Control header as an integer diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 7fec7246d58..a92c18fc78c 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -104,7 +104,10 @@ Minor features 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 ~~~~ diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 2bd25afd422..ce030d4221f 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -1277,6 +1277,7 @@ Here are some more examples: * ``no_transform=True`` * ``must_revalidate=True`` * ``stale_while_revalidate=num_seconds`` +* ``no_cache=True`` The full list of known directives can be found in the `IANA registry`_ (note that not all of them apply to responses). diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 648ffbc4995..8182d5a8c0c 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -1705,6 +1705,12 @@ class CacheUtils(SimpleTestCase): ('', {'no-cache': 'Set-Cookie'}, {'no-cache=Set-Cookie'}), ('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=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 ('private', {'private': True}, {'private'}), ('private', {'public': True}, {'public'}),