Commits

Anonymous committed 01e3c15

Fixed #17476 -- Ensure timezone-dependant cache keys only use ASCII characters, especially on Windows.

Comments (0)

Files changed (2)

django/utils/cache.py

 
 from django.conf import settings
 from django.core.cache import get_cache
-from django.utils.encoding import smart_str, iri_to_uri
+from django.utils.encoding import smart_str, iri_to_uri, force_unicode
 from django.utils.http import http_date
 from django.utils.timezone import get_current_timezone_name
 from django.utils.translation import get_language
         # which in turn can also fall back to settings.LANGUAGE_CODE
         cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
     if settings.USE_TZ:
-        # Windows uses non-standard timezone names that may include spaces,
-        # which triggers CacheKeyWarning.
-        cache_key += '.%s' % get_current_timezone_name().replace(' ', '_')
+        # The datetime module doesn't restrict the output of tzname().
+        # Windows is known to use non-standard, locale-dependant names.
+        # User-defined tzinfo classes may return absolutely anything.
+        # Hence this paranoid conversion to create a valid cache key.
+        tz_name = force_unicode(get_current_timezone_name(), errors='ignore')
+        cache_key += '.%s' % tz_name.encode('ascii', 'ignore').replace(' ', '_')
     return cache_key
 
 def _generate_cache_key(request, method, headerlist, key_prefix):

tests/regressiontests/cache/tests.py

 from django.utils import timezone, translation, unittest
 from django.utils.cache import (patch_vary_headers, get_cache_key,
     learn_cache_key, patch_cache_control, patch_response_headers)
+from django.utils.encoding import force_unicode
 from django.views.decorators.cache import cache_page
 
 from .models import Poll, expensive_calculation
     @override_settings(USE_I18N=False, USE_L10N=False, USE_TZ=True)
     def test_cache_key_i18n_timezone(self):
         request = self._get_request()
-        tz = timezone.get_current_timezone_name().replace(' ', '_')
+        # This is tightly coupled to the implementation,
+        # but it's the most straightforward way to test the key.
+        tz = force_unicode(timezone.get_current_timezone_name(), errors='ignore')
+        tz = tz.encode('ascii', 'ignore').replace(' ', '_')
         response = HttpResponse()
         key = learn_cache_key(request, response)
         self.assertIn(tz, key, "Cache keys should include the time zone name when time zones are active")
     def test_cache_key_no_i18n (self):
         request = self._get_request()
         lang = translation.get_language()
-        tz = timezone.get_current_timezone_name().replace(' ', '_')
+        tz = force_unicode(timezone.get_current_timezone_name(), errors='ignore')
+        tz = tz.encode('ascii', 'ignore').replace(' ', '_')
         response = HttpResponse()
         key = learn_cache_key(request, response)
         self.assertNotIn(lang, key, "Cache keys shouldn't include the language name when i18n isn't active")
         self.assertNotIn(tz, key, "Cache keys shouldn't include the time zone name when i18n isn't active")
 
+    @override_settings(USE_I18N=False, USE_L10N=False, USE_TZ=True)
+    def test_cache_key_with_non_ascii_tzname(self):
+        # Regression test for #17476
+        class CustomTzName(timezone.UTC):
+            name = ''
+            def tzname(self, dt):
+                return self.name
+
+        request = self._get_request()
+        response = HttpResponse()
+        with timezone.override(CustomTzName()):
+            CustomTzName.name = 'Hora estándar de Argentina'    # UTF-8 string
+            sanitized_name = 'Hora_estndar_de_Argentina'
+            self.assertIn(sanitized_name, learn_cache_key(request, response),
+                    "Cache keys should include the time zone name when time zones are active")
+
+            CustomTzName.name = u'Hora estándar de Argentina'    # unicode
+            sanitized_name = 'Hora_estndar_de_Argentina'
+            self.assertIn(sanitized_name, learn_cache_key(request, response),
+                    "Cache keys should include the time zone name when time zones are active")
+
+
     @override_settings(
             CACHE_MIDDLEWARE_KEY_PREFIX="test",
             CACHE_MIDDLEWARE_SECONDS=60,