Browse Source

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
Flavio Curella 5 years ago
parent
commit
ed112fadc1
5 changed files with 43 additions and 8 deletions
  1. 27 7
      django/utils/cache.py
  2. 5 0
      docs/ref/utils.txt
  3. 4 1
      docs/releases/3.1.txt
  4. 1 0
      docs/topics/cache.txt
  5. 6 0
      tests/cache/tests.py

+ 27 - 7
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
 
 

+ 5 - 0
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

+ 4 - 1
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
 ~~~~

+ 1 - 0
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).

+ 6 - 0
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'}),