diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
|
a
|
b
|
|
| 5 | 5 | class InvalidCacheBackendError(ImproperlyConfigured): |
| 6 | 6 | pass |
| 7 | 7 | |
| | 8 | class InvalidCacheKeyError(Exception): |
| | 9 | pass |
| | 10 | |
| | 11 | # memcached does not accept keys longer than 250 characters |
| | 12 | MAX_KEY_LENGTH = 250 |
| | 13 | |
| 8 | 14 | class BaseCache(object): |
| 9 | 15 | def __init__(self, params): |
| 10 | 16 | timeout = params.get('timeout', 300) |
| … |
… |
|
| 116 | 122 | def clear(self): |
| 117 | 123 | """Remove *all* values from the cache at once.""" |
| 118 | 124 | raise NotImplementedError |
| | 125 | |
| | 126 | def _validate_key(self, key): |
| | 127 | """ |
| | 128 | Enforce keys that would be valid on all backends, to keep cache code portable. |
| | 129 | |
| | 130 | """ |
| | 131 | if len(key) > MAX_KEY_LENGTH: |
| | 132 | raise InvalidCacheKeyError('Key is too long (max length %s)' % MAX_KEY_LENGTH) |
| | 133 | for char in key: |
| | 134 | if ord(char) < 33 or ord(char) == 127: |
| | 135 | raise InvalidCacheKeyError('Key contains control character') |
| | 136 | |
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
|
a
|
b
|
|
| 46 | 46 | self._cull_frequency = 3 |
| 47 | 47 | |
| 48 | 48 | def get(self, key, default=None): |
| | 49 | self._validate_key(key) |
| 49 | 50 | db = router.db_for_read(self.cache_model_class) |
| 50 | 51 | table = connections[db].ops.quote_name(self._table) |
| 51 | 52 | cursor = connections[db].cursor() |
| … |
… |
|
| 65 | 66 | return pickle.loads(base64.decodestring(value)) |
| 66 | 67 | |
| 67 | 68 | def set(self, key, value, timeout=None): |
| | 69 | self._validate_key(key) |
| 68 | 70 | self._base_set('set', key, value, timeout) |
| 69 | 71 | |
| 70 | 72 | def add(self, key, value, timeout=None): |
| | 73 | self._validate_key(key) |
| 71 | 74 | return self._base_set('add', key, value, timeout) |
| 72 | 75 | |
| 73 | 76 | def _base_set(self, mode, key, value, timeout=None): |
| … |
… |
|
| 103 | 106 | return True |
| 104 | 107 | |
| 105 | 108 | def delete(self, key): |
| | 109 | self._validate_key(key) |
| 106 | 110 | db = router.db_for_write(self.cache_model_class) |
| 107 | 111 | table = connections[db].ops.quote_name(self._table) |
| 108 | 112 | cursor = connections[db].cursor() |
| … |
… |
|
| 111 | 115 | transaction.commit_unless_managed(using=db) |
| 112 | 116 | |
| 113 | 117 | def has_key(self, key): |
| | 118 | self._validate_key(key) |
| 114 | 119 | db = router.db_for_read(self.cache_model_class) |
| 115 | 120 | table = connections[db].ops.quote_name(self._table) |
| 116 | 121 | cursor = connections[db].cursor() |
diff --git a/django/core/cache/backends/dummy.py b/django/core/cache/backends/dummy.py
|
a
|
b
|
|
| 6 | 6 | def __init__(self, *args, **kwargs): |
| 7 | 7 | pass |
| 8 | 8 | |
| 9 | | def add(self, *args, **kwargs): |
| | 9 | def add(self, key, *args, **kwargs): |
| | 10 | self._validate_key(key) |
| 10 | 11 | return True |
| 11 | 12 | |
| 12 | 13 | def get(self, key, default=None): |
| | 14 | self._validate_key(key) |
| 13 | 15 | return default |
| 14 | 16 | |
| 15 | | def set(self, *args, **kwargs): |
| 16 | | pass |
| | 17 | def set(self, key, *args, **kwargs): |
| | 18 | self._validate_key(key) |
| 17 | 19 | |
| 18 | | def delete(self, *args, **kwargs): |
| 19 | | pass |
| | 20 | def delete(self, key, *args, **kwargs): |
| | 21 | self._validate_key(key) |
| 20 | 22 | |
| 21 | 23 | def get_many(self, *args, **kwargs): |
| 22 | 24 | return {} |
| 23 | 25 | |
| 24 | | def has_key(self, *args, **kwargs): |
| | 26 | def has_key(self, key, *args, **kwargs): |
| | 27 | self._validate_key(key) |
| 25 | 28 | return False |
| 26 | 29 | |
| 27 | 30 | def set_many(self, *args, **kwargs): |
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
|
a
|
b
|
|
| 32 | 32 | self._createdir() |
| 33 | 33 | |
| 34 | 34 | def add(self, key, value, timeout=None): |
| | 35 | self._validate_key(key) |
| 35 | 36 | if self.has_key(key): |
| 36 | 37 | return False |
| 37 | 38 | |
| … |
… |
|
| 39 | 40 | return True |
| 40 | 41 | |
| 41 | 42 | def get(self, key, default=None): |
| | 43 | self._validate_key(key) |
| 42 | 44 | fname = self._key_to_file(key) |
| 43 | 45 | try: |
| 44 | 46 | f = open(fname, 'rb') |
| … |
… |
|
| 56 | 58 | return default |
| 57 | 59 | |
| 58 | 60 | def set(self, key, value, timeout=None): |
| | 61 | self._validate_key(key) |
| 59 | 62 | fname = self._key_to_file(key) |
| 60 | 63 | dirname = os.path.dirname(fname) |
| 61 | 64 | |
| … |
… |
|
| 79 | 82 | pass |
| 80 | 83 | |
| 81 | 84 | def delete(self, key): |
| | 85 | self._validate_key(key) |
| 82 | 86 | try: |
| 83 | 87 | self._delete(self._key_to_file(key)) |
| 84 | 88 | except (IOError, OSError): |
| … |
… |
|
| 95 | 99 | pass |
| 96 | 100 | |
| 97 | 101 | def has_key(self, key): |
| | 102 | self._validate_key(key) |
| 98 | 103 | fname = self._key_to_file(key) |
| 99 | 104 | try: |
| 100 | 105 | f = open(fname, 'rb') |
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
|
a
|
b
|
|
| 30 | 30 | self._lock = RWLock() |
| 31 | 31 | |
| 32 | 32 | def add(self, key, value, timeout=None): |
| | 33 | self._validate_key(key) |
| 33 | 34 | self._lock.writer_enters() |
| 34 | 35 | try: |
| 35 | 36 | exp = self._expire_info.get(key) |
| … |
… |
|
| 44 | 45 | self._lock.writer_leaves() |
| 45 | 46 | |
| 46 | 47 | def get(self, key, default=None): |
| | 48 | self._validate_key(key) |
| 47 | 49 | self._lock.reader_enters() |
| 48 | 50 | try: |
| 49 | 51 | exp = self._expire_info.get(key) |
| … |
… |
|
| 76 | 78 | self._expire_info[key] = time.time() + timeout |
| 77 | 79 | |
| 78 | 80 | def set(self, key, value, timeout=None): |
| | 81 | self._validate_key(key) |
| 79 | 82 | self._lock.writer_enters() |
| 80 | 83 | # Python 2.4 doesn't allow combined try-except-finally blocks. |
| 81 | 84 | try: |
| … |
… |
|
| 87 | 90 | self._lock.writer_leaves() |
| 88 | 91 | |
| 89 | 92 | def has_key(self, key): |
| | 93 | self._validate_key(key) |
| 90 | 94 | self._lock.reader_enters() |
| 91 | 95 | try: |
| 92 | 96 | exp = self._expire_info.get(key) |
| … |
… |
|
| 127 | 131 | pass |
| 128 | 132 | |
| 129 | 133 | def delete(self, key): |
| | 134 | self._validate_key(key) |
| 130 | 135 | self._lock.writer_enters() |
| 131 | 136 | try: |
| 132 | 137 | self._delete(key) |
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
|
a
|
b
|
|
| 537 | 537 | >>> cache.get('my_key') |
| 538 | 538 | 'hello, world!' |
| 539 | 539 | |
| | 540 | .. note:: |
| | 541 | |
| | 542 | Cache keys may not be longer than 250 characters, and may not |
| | 543 | contain whitespace or control characters. |
| | 544 | |
| 540 | 545 | The ``timeout`` argument is optional and defaults to the ``timeout`` |
| 541 | 546 | argument in the ``CACHE_BACKEND`` setting (explained above). It's the number of |
| 542 | 547 | seconds the value should be stored in the cache. |
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
|
a
|
b
|
|
| 366 | 366 | count = count + 1 |
| 367 | 367 | self.assertEqual(count, final_count) |
| 368 | 368 | |
| | 369 | def test_invalid_keys(self): |
| | 370 | """ |
| | 371 | All the builtin backends should refuse keys that would be refused by |
| | 372 | memcached, so code using the cache API is portable. Refs #6447. |
| | 373 | |
| | 374 | We assert only that a generic exception is raised because we don't |
| | 375 | want to encumber the memcached backend with key-checking code, and the |
| | 376 | specific exceptions raised from memcached will depend on the client |
| | 377 | library used. |
| | 378 | |
| | 379 | """ |
| | 380 | self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value') |
| | 381 | # memcached also limits key length to 250 |
| | 382 | self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value') |
| | 383 | |
| 369 | 384 | class DBCacheTests(unittest.TestCase, BaseCacheTests): |
| 370 | 385 | def setUp(self): |
| 371 | 386 | # Spaces are used in the table name to ensure quoting/escaping is working |