Skip to content

Commit

Permalink
Fixed #30024 -- Made urlencode() and Client raise TypeError when None…
Browse files Browse the repository at this point in the history
… is passed as data.
  • Loading branch information
jdufresne authored and timgraham committed Dec 27, 2018
1 parent 293db9e commit 6fe9c45
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 8 deletions.
7 changes: 6 additions & 1 deletion django/test/client.py
Expand Up @@ -192,7 +192,12 @@ def is_file(thing):
# file, or a *list* of form values and/or files. Remember that HTTP field
# names can be duplicated!
for (key, value) in data.items():
if is_file(value):
if value is None:
raise TypeError(
'Cannot encode None as POST data. Did you mean to pass an '
'empty string or omit the value?'
)
elif is_file(value):
lines.extend(encode_file(boundary, key, value))
elif not isinstance(value, str) and is_iterable(value):
for item in value:
Expand Down
23 changes: 17 additions & 6 deletions django/utils/http.py
Expand Up @@ -91,20 +91,31 @@ def urlencode(query, doseq=False):
query = query.items()
query_params = []
for key, value in query:
if isinstance(value, (str, bytes)):
if value is None:
raise TypeError(
'Cannot encode None in a query string. Did you mean to pass '
'an empty string or omit the value?'
)
elif isinstance(value, (str, bytes)):
query_val = value
else:
try:
iter(value)
itr = iter(value)
except TypeError:
query_val = value
else:
# Consume generators and iterators, even when doseq=True, to
# work around https://bugs.python.org/issue31706.
query_val = [
item if isinstance(item, bytes) else str(item)
for item in value
]
query_val = []
for item in itr:
if item is None:
raise TypeError(
'Cannot encode None in a query string. Did you '
'mean to pass an empty string or omit the value?'
)
elif not isinstance(item, bytes):
item = str(item)
query_val.append(item)
query_params.append((key, query_val))
return original_urlencode(query_params, doseq)

Expand Down
5 changes: 5 additions & 0 deletions docs/releases/2.2.txt
Expand Up @@ -457,6 +457,11 @@ Miscellaneous
* Tests that violate deferrable database constraints now error when run on
SQLite 3.20+, just like on other backends that support such constraints.

* To catch usage mistakes, the test :class:`~django.test.Client` and
:func:`django.utils.http.urlencode` now raise ``TypeError`` if ``None`` is
passed as a value to encode because ``None`` can't be encoded in GET and POST
data. Either pass an empty string or omit the value.

.. _deprecated-features-2.2:

Features deprecated in 2.2
Expand Down
16 changes: 16 additions & 0 deletions tests/test_client/tests.py
Expand Up @@ -59,6 +59,14 @@ def test_query_string_encoding(self):
response = self.client.get('/get_view/?var=1\ufffd')
self.assertEqual(response.context['var'], '1\ufffd')

def test_get_data_none(self):
msg = (
'Cannot encode None in a query string. Did you mean to pass an '
'empty string or omit the value?'
)
with self.assertRaisesMessage(TypeError, msg):
self.client.get('/get_view/', {'value': None})

def test_get_post_view(self):
"GET a view that normally expects POSTs"
response = self.client.get('/post_view/', {})
Expand Down Expand Up @@ -92,6 +100,14 @@ def test_post(self):
self.assertEqual(response.templates[0].name, 'POST Template')
self.assertContains(response, 'Data received')

def test_post_data_none(self):
msg = (
'Cannot encode None as POST data. Did you mean to pass an empty '
'string or omit the value?'
)
with self.assertRaisesMessage(TypeError, msg):
self.client.post('/post_view/', {'value': None})

def test_json_serialization(self):
"""The test client serializes JSON data."""
methods = ('post', 'put', 'patch', 'delete')
Expand Down
21 changes: 20 additions & 1 deletion tests/utils_tests/test_http.py
Expand Up @@ -12,7 +12,12 @@
)


class URLEncodeTests(unittest.TestCase):
class URLEncodeTests(SimpleTestCase):
cannot_encode_none_msg = (
'Cannot encode None in a query string. Did you mean to pass an '
'empty string or omit the value?'
)

def test_tuples(self):
self.assertEqual(urlencode((('a', 1), ('b', 2), ('c', 3))), 'a=1&b=2&c=3')

Expand Down Expand Up @@ -65,6 +70,20 @@ def gen():
self.assertEqual(urlencode({'a': gen()}, doseq=True), 'a=0&a=1')
self.assertEqual(urlencode({'a': gen()}, doseq=False), 'a=%5B%270%27%2C+%271%27%5D')

def test_none(self):
with self.assertRaisesMessage(TypeError, self.cannot_encode_none_msg):
urlencode({'a': None})

def test_none_in_sequence(self):
with self.assertRaisesMessage(TypeError, self.cannot_encode_none_msg):
urlencode({'a': [None]}, doseq=True)

def test_none_in_generator(self):
def gen():
yield None
with self.assertRaisesMessage(TypeError, self.cannot_encode_none_msg):
urlencode({'a': gen()}, doseq=True)


class Base36IntTests(SimpleTestCase):
def test_roundtrip(self):
Expand Down

0 comments on commit 6fe9c45

Please sign in to comment.