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

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17286 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Aymeric Augustin 2011-12-29 13:57:32 +00:00
parent ec07a30e82
commit 3367913c3d
2 changed files with 36 additions and 6 deletions

View File

@ -23,7 +23,7 @@ import time
from django.conf import settings from django.conf import settings
from django.core.cache import get_cache 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.http import http_date
from django.utils.timezone import get_current_timezone_name from django.utils.timezone import get_current_timezone_name
from django.utils.translation import get_language from django.utils.translation import get_language
@ -165,9 +165,12 @@ def _i18n_cache_key_suffix(request, cache_key):
# which in turn can also fall back to settings.LANGUAGE_CODE # which in turn can also fall back to settings.LANGUAGE_CODE
cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language()) cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
if settings.USE_TZ: if settings.USE_TZ:
# Windows uses non-standard timezone names that may include spaces, # The datetime module doesn't restrict the output of tzname().
# which triggers CacheKeyWarning. # Windows is known to use non-standard, locale-dependant names.
cache_key += '.%s' % get_current_timezone_name().replace(' ', '_') # 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 return cache_key
def _generate_cache_key(request, method, headerlist, key_prefix): def _generate_cache_key(request, method, headerlist, key_prefix):

View File

@ -28,6 +28,7 @@ from django.test.utils import (get_warnings_state, restore_warnings_state,
from django.utils import timezone, translation, unittest from django.utils import timezone, translation, unittest
from django.utils.cache import (patch_vary_headers, get_cache_key, from django.utils.cache import (patch_vary_headers, get_cache_key,
learn_cache_key, patch_cache_control, patch_response_headers) 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 django.views.decorators.cache import cache_page
from .models import Poll, expensive_calculation from .models import Poll, expensive_calculation
@ -1270,7 +1271,10 @@ class CacheI18nTest(TestCase):
@override_settings(USE_I18N=False, USE_L10N=False, USE_TZ=True) @override_settings(USE_I18N=False, USE_L10N=False, USE_TZ=True)
def test_cache_key_i18n_timezone(self): def test_cache_key_i18n_timezone(self):
request = self._get_request() 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() response = HttpResponse()
key = learn_cache_key(request, response) key = learn_cache_key(request, response)
self.assertIn(tz, key, "Cache keys should include the time zone name when time zones are active") self.assertIn(tz, key, "Cache keys should include the time zone name when time zones are active")
@ -1281,12 +1285,35 @@ class CacheI18nTest(TestCase):
def test_cache_key_no_i18n (self): def test_cache_key_no_i18n (self):
request = self._get_request() request = self._get_request()
lang = translation.get_language() 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() response = HttpResponse()
key = learn_cache_key(request, response) 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(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") 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( @override_settings(
CACHE_MIDDLEWARE_KEY_PREFIX="test", CACHE_MIDDLEWARE_KEY_PREFIX="test",
CACHE_MIDDLEWARE_SECONDS=60, CACHE_MIDDLEWARE_SECONDS=60,