From 727d7ce6cba21363470aaefb2dc5353017531be3 Mon Sep 17 00:00:00 2001 From: Jani Tiainen Date: Sat, 10 Sep 2016 12:25:42 +0300 Subject: [PATCH] Fixed #27198 -- Made MultiValueDict.getlist() return a new list to prevent mutation. --- django/utils/datastructures.py | 25 ++++++++++++++++++------ tests/utils_tests/test_datastructures.py | 18 +++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 76f7459032..1673ee10e8 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -109,7 +109,7 @@ class MultiValueDict(dict): def __getstate__(self): obj_dict = self.__dict__.copy() - obj_dict['_data'] = {k: self.getlist(k) for k in self} + obj_dict['_data'] = {k: self._getlist(k) for k in self} return obj_dict def __setstate__(self, obj_dict): @@ -131,17 +131,30 @@ class MultiValueDict(dict): return default return val - def getlist(self, key, default=None): + def _getlist(self, key, default=None, force_list=False): """ - Returns the list of values for the passed key. If key doesn't exist, - then a default value is returned. + Return a list of values for the key. + + Used internally to manipulate values list. If force_list is True, + return a new copy of values. """ try: - return super(MultiValueDict, self).__getitem__(key) + values = super(MultiValueDict, self).__getitem__(key) except KeyError: if default is None: return [] return default + else: + if force_list: + values = list(values) + return values + + def getlist(self, key, default=None): + """ + Return the list of values for the key. If key doesn't exist, return a + default value. + """ + return self._getlist(key, default, force_list=True) def setlist(self, key, list_): super(MultiValueDict, self).__setitem__(key, list_) @@ -160,7 +173,7 @@ class MultiValueDict(dict): self.setlist(key, default_list) # Do not return default_list here because setlist() may store # another value -- QueryDict.setlist() does. Look it up. - return self.getlist(key) + return self._getlist(key) def appendlist(self, key, value): """Appends an item to the internal list associated with key.""" diff --git a/tests/utils_tests/test_datastructures.py b/tests/utils_tests/test_datastructures.py index 0f520ff9e7..ea2b147953 100644 --- a/tests/utils_tests/test_datastructures.py +++ b/tests/utils_tests/test_datastructures.py @@ -101,6 +101,24 @@ class MultiValueDictTests(SimpleTestCase): self.assertEqual({}, MultiValueDict().dict()) + def test_getlist_doesnt_mutate(self): + x = MultiValueDict({'a': ['1', '2'], 'b': ['3']}) + values = x.getlist('a') + values += x.getlist('b') + self.assertEqual(x.getlist('a'), ['1', '2']) + + def test_internal_getlist_does_mutate(self): + x = MultiValueDict({'a': ['1', '2'], 'b': ['3']}) + values = x._getlist('a') + values += x._getlist('b') + self.assertEqual(x._getlist('a'), ['1', '2', '3']) + + def test_getlist_default(self): + x = MultiValueDict({'a': [1]}) + MISSING = object() + values = x.getlist('b', default=MISSING) + self.assertIs(values, MISSING) + class ImmutableListTests(SimpleTestCase):