tests.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. # -*- encoding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import copy
  4. import os
  5. import pickle
  6. from django.core.exceptions import SuspiciousOperation
  7. from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
  8. HttpResponsePermanentRedirect, HttpResponseNotAllowed,
  9. HttpResponseNotModified, StreamingHttpResponse,
  10. SimpleCookie, BadHeaderError,
  11. parse_cookie)
  12. from django.test import TestCase
  13. from django.utils.encoding import smart_str
  14. from django.utils import six
  15. from django.utils import unittest
  16. class QueryDictTests(unittest.TestCase):
  17. def test_missing_key(self):
  18. q = QueryDict(str(''))
  19. self.assertRaises(KeyError, q.__getitem__, 'foo')
  20. def test_immutability(self):
  21. q = QueryDict(str(''))
  22. self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
  23. self.assertRaises(AttributeError, q.setlist, 'foo', ['bar'])
  24. self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
  25. self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
  26. self.assertRaises(AttributeError, q.pop, 'foo')
  27. self.assertRaises(AttributeError, q.popitem)
  28. self.assertRaises(AttributeError, q.clear)
  29. def test_immutable_get_with_default(self):
  30. q = QueryDict(str(''))
  31. self.assertEqual(q.get('foo', 'default'), 'default')
  32. def test_immutable_basic_operations(self):
  33. q = QueryDict(str(''))
  34. self.assertEqual(q.getlist('foo'), [])
  35. if not six.PY3:
  36. self.assertEqual(q.has_key('foo'), False)
  37. self.assertEqual('foo' in q, False)
  38. self.assertEqual(list(six.iteritems(q)), [])
  39. self.assertEqual(list(six.iterlists(q)), [])
  40. self.assertEqual(list(six.iterkeys(q)), [])
  41. self.assertEqual(list(six.itervalues(q)), [])
  42. self.assertEqual(len(q), 0)
  43. self.assertEqual(q.urlencode(), '')
  44. def test_single_key_value(self):
  45. """Test QueryDict with one key/value pair"""
  46. q = QueryDict(str('foo=bar'))
  47. self.assertEqual(q['foo'], 'bar')
  48. self.assertRaises(KeyError, q.__getitem__, 'bar')
  49. self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
  50. self.assertEqual(q.get('foo', 'default'), 'bar')
  51. self.assertEqual(q.get('bar', 'default'), 'default')
  52. self.assertEqual(q.getlist('foo'), ['bar'])
  53. self.assertEqual(q.getlist('bar'), [])
  54. self.assertRaises(AttributeError, q.setlist, 'foo', ['bar'])
  55. self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
  56. if not six.PY3:
  57. self.assertTrue(q.has_key('foo'))
  58. self.assertTrue('foo' in q)
  59. if not six.PY3:
  60. self.assertFalse(q.has_key('bar'))
  61. self.assertFalse('bar' in q)
  62. self.assertEqual(list(six.iteritems(q)), [('foo', 'bar')])
  63. self.assertEqual(list(six.iterlists(q)), [('foo', ['bar'])])
  64. self.assertEqual(list(six.iterkeys(q)), ['foo'])
  65. self.assertEqual(list(six.itervalues(q)), ['bar'])
  66. self.assertEqual(len(q), 1)
  67. self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
  68. self.assertRaises(AttributeError, q.pop, 'foo')
  69. self.assertRaises(AttributeError, q.popitem)
  70. self.assertRaises(AttributeError, q.clear)
  71. self.assertRaises(AttributeError, q.setdefault, 'foo', 'bar')
  72. self.assertEqual(q.urlencode(), 'foo=bar')
  73. def test_urlencode(self):
  74. q = QueryDict(str(''), mutable=True)
  75. q['next'] = '/a&b/'
  76. self.assertEqual(q.urlencode(), 'next=%2Fa%26b%2F')
  77. self.assertEqual(q.urlencode(safe='/'), 'next=/a%26b/')
  78. q = QueryDict(str(''), mutable=True)
  79. q['next'] = '/t\xebst&key/'
  80. self.assertEqual(q.urlencode(), 'next=%2Ft%C3%ABst%26key%2F')
  81. self.assertEqual(q.urlencode(safe='/'), 'next=/t%C3%ABst%26key/')
  82. def test_mutable_copy(self):
  83. """A copy of a QueryDict is mutable."""
  84. q = QueryDict(str('')).copy()
  85. self.assertRaises(KeyError, q.__getitem__, "foo")
  86. q['name'] = 'john'
  87. self.assertEqual(q['name'], 'john')
  88. def test_mutable_delete(self):
  89. q = QueryDict(str('')).copy()
  90. q['name'] = 'john'
  91. del q['name']
  92. self.assertFalse('name' in q)
  93. def test_basic_mutable_operations(self):
  94. q = QueryDict(str('')).copy()
  95. q['name'] = 'john'
  96. self.assertEqual(q.get('foo', 'default'), 'default')
  97. self.assertEqual(q.get('name', 'default'), 'john')
  98. self.assertEqual(q.getlist('name'), ['john'])
  99. self.assertEqual(q.getlist('foo'), [])
  100. q.setlist('foo', ['bar', 'baz'])
  101. self.assertEqual(q.get('foo', 'default'), 'baz')
  102. self.assertEqual(q.getlist('foo'), ['bar', 'baz'])
  103. q.appendlist('foo', 'another')
  104. self.assertEqual(q.getlist('foo'), ['bar', 'baz', 'another'])
  105. self.assertEqual(q['foo'], 'another')
  106. if not six.PY3:
  107. self.assertTrue(q.has_key('foo'))
  108. self.assertTrue('foo' in q)
  109. self.assertEqual(list(six.iteritems(q)), [('foo', 'another'), ('name', 'john')])
  110. self.assertEqual(list(six.iterlists(q)), [('foo', ['bar', 'baz', 'another']), ('name', ['john'])])
  111. self.assertEqual(list(six.iterkeys(q)), ['foo', 'name'])
  112. self.assertEqual(list(six.itervalues(q)), ['another', 'john'])
  113. self.assertEqual(len(q), 2)
  114. q.update({'foo': 'hello'})
  115. self.assertEqual(q['foo'], 'hello')
  116. self.assertEqual(q.get('foo', 'not available'), 'hello')
  117. self.assertEqual(q.getlist('foo'), ['bar', 'baz', 'another', 'hello'])
  118. self.assertEqual(q.pop('foo'), ['bar', 'baz', 'another', 'hello'])
  119. self.assertEqual(q.pop('foo', 'not there'), 'not there')
  120. self.assertEqual(q.get('foo', 'not there'), 'not there')
  121. self.assertEqual(q.setdefault('foo', 'bar'), 'bar')
  122. self.assertEqual(q['foo'], 'bar')
  123. self.assertEqual(q.getlist('foo'), ['bar'])
  124. self.assertEqual(q.urlencode(), 'foo=bar&name=john')
  125. q.clear()
  126. self.assertEqual(len(q), 0)
  127. def test_multiple_keys(self):
  128. """Test QueryDict with two key/value pairs with same keys."""
  129. q = QueryDict(str('vote=yes&vote=no'))
  130. self.assertEqual(q['vote'], 'no')
  131. self.assertRaises(AttributeError, q.__setitem__, 'something', 'bar')
  132. self.assertEqual(q.get('vote', 'default'), 'no')
  133. self.assertEqual(q.get('foo', 'default'), 'default')
  134. self.assertEqual(q.getlist('vote'), ['yes', 'no'])
  135. self.assertEqual(q.getlist('foo'), [])
  136. self.assertRaises(AttributeError, q.setlist, 'foo', ['bar', 'baz'])
  137. self.assertRaises(AttributeError, q.setlist, 'foo', ['bar', 'baz'])
  138. self.assertRaises(AttributeError, q.appendlist, 'foo', ['bar'])
  139. if not six.PY3:
  140. self.assertEqual(q.has_key('vote'), True)
  141. self.assertEqual('vote' in q, True)
  142. if not six.PY3:
  143. self.assertEqual(q.has_key('foo'), False)
  144. self.assertEqual('foo' in q, False)
  145. self.assertEqual(list(six.iteritems(q)), [('vote', 'no')])
  146. self.assertEqual(list(six.iterlists(q)), [('vote', ['yes', 'no'])])
  147. self.assertEqual(list(six.iterkeys(q)), ['vote'])
  148. self.assertEqual(list(six.itervalues(q)), ['no'])
  149. self.assertEqual(len(q), 1)
  150. self.assertRaises(AttributeError, q.update, {'foo': 'bar'})
  151. self.assertRaises(AttributeError, q.pop, 'foo')
  152. self.assertRaises(AttributeError, q.popitem)
  153. self.assertRaises(AttributeError, q.clear)
  154. self.assertRaises(AttributeError, q.setdefault, 'foo', 'bar')
  155. self.assertRaises(AttributeError, q.__delitem__, 'vote')
  156. if not six.PY3:
  157. def test_invalid_input_encoding(self):
  158. """
  159. QueryDicts must be able to handle invalid input encoding (in this
  160. case, bad UTF-8 encoding).
  161. This test doesn't apply under Python 3 because the URL is a string
  162. and not a bytestring.
  163. """
  164. q = QueryDict(str(b'foo=bar&foo=\xff'))
  165. self.assertEqual(q['foo'], '\ufffd')
  166. self.assertEqual(q.getlist('foo'), ['bar', '\ufffd'])
  167. def test_pickle(self):
  168. q = QueryDict(str(''))
  169. q1 = pickle.loads(pickle.dumps(q, 2))
  170. self.assertEqual(q == q1, True)
  171. q = QueryDict(str('a=b&c=d'))
  172. q1 = pickle.loads(pickle.dumps(q, 2))
  173. self.assertEqual(q == q1, True)
  174. q = QueryDict(str('a=b&c=d&a=1'))
  175. q1 = pickle.loads(pickle.dumps(q, 2))
  176. self.assertEqual(q == q1, True)
  177. def test_update_from_querydict(self):
  178. """Regression test for #8278: QueryDict.update(QueryDict)"""
  179. x = QueryDict(str("a=1&a=2"), mutable=True)
  180. y = QueryDict(str("a=3&a=4"))
  181. x.update(y)
  182. self.assertEqual(x.getlist('a'), ['1', '2', '3', '4'])
  183. def test_non_default_encoding(self):
  184. """#13572 - QueryDict with a non-default encoding"""
  185. q = QueryDict(str('cur=%A4'), encoding='iso-8859-15')
  186. self.assertEqual(q.encoding, 'iso-8859-15')
  187. self.assertEqual(list(six.iteritems(q)), [('cur', '€')])
  188. self.assertEqual(q.urlencode(), 'cur=%A4')
  189. q = q.copy()
  190. self.assertEqual(q.encoding, 'iso-8859-15')
  191. self.assertEqual(list(six.iteritems(q)), [('cur', '€')])
  192. self.assertEqual(q.urlencode(), 'cur=%A4')
  193. self.assertEqual(copy.copy(q).encoding, 'iso-8859-15')
  194. self.assertEqual(copy.deepcopy(q).encoding, 'iso-8859-15')
  195. class HttpResponseTests(unittest.TestCase):
  196. def test_headers_type(self):
  197. r = HttpResponse()
  198. # The following tests explicitly test types in addition to values
  199. # because in Python 2 u'foo' == b'foo'.
  200. # ASCII unicode or bytes values are converted to native strings.
  201. r['key'] = 'test'
  202. self.assertEqual(r['key'], str('test'))
  203. self.assertIsInstance(r['key'], str)
  204. r['key'] = 'test'.encode('ascii')
  205. self.assertEqual(r['key'], str('test'))
  206. self.assertIsInstance(r['key'], str)
  207. # Latin-1 unicode or bytes values are also converted to native strings.
  208. r['key'] = 'café'
  209. self.assertEqual(r['key'], smart_str('café', 'latin-1'))
  210. self.assertIsInstance(r['key'], str)
  211. r['key'] = 'café'.encode('latin-1')
  212. self.assertEqual(r['key'], smart_str('café', 'latin-1'))
  213. self.assertIsInstance(r['key'], str)
  214. # Other unicode values are MIME-encoded (there's no way to pass them as bytes).
  215. r['key'] = '†'
  216. self.assertEqual(r['key'], str('=?utf-8?b?4oCg?='))
  217. self.assertIsInstance(r['key'], str)
  218. # The response also converts unicode or bytes keys to strings, but requires
  219. # them to contain ASCII
  220. r = HttpResponse()
  221. r['foo'] = 'bar'
  222. l = list(r.items())
  223. self.assertEqual(l[0], ('foo', 'bar'))
  224. self.assertIsInstance(l[0][0], str)
  225. r = HttpResponse()
  226. r[b'foo'] = 'bar'
  227. l = list(r.items())
  228. self.assertEqual(l[0], ('foo', 'bar'))
  229. self.assertIsInstance(l[0][0], str)
  230. r = HttpResponse()
  231. self.assertRaises(UnicodeError, r.__setitem__, 'føø', 'bar')
  232. self.assertRaises(UnicodeError, r.__setitem__, 'føø'.encode('utf-8'), 'bar')
  233. def test_newlines_in_headers(self):
  234. # Bug #10188: Do not allow newlines in headers (CR or LF)
  235. r = HttpResponse()
  236. self.assertRaises(BadHeaderError, r.__setitem__, 'test\rstr', 'test')
  237. self.assertRaises(BadHeaderError, r.__setitem__, 'test\nstr', 'test')
  238. def test_dict_behavior(self):
  239. """
  240. Test for bug #14020: Make HttpResponse.get work like dict.get
  241. """
  242. r = HttpResponse()
  243. self.assertEqual(r.get('test'), None)
  244. def test_non_string_content(self):
  245. #Bug 16494: HttpResponse should behave consistently with non-strings
  246. r = HttpResponse(12345)
  247. self.assertEqual(r.content, b'12345')
  248. #test content via property
  249. r = HttpResponse()
  250. r.content = 12345
  251. self.assertEqual(r.content, b'12345')
  252. def test_iter_content(self):
  253. r = HttpResponse(['abc', 'def', 'ghi'])
  254. self.assertEqual(r.content, b'abcdefghi')
  255. #test iter content via property
  256. r = HttpResponse()
  257. r.content = ['idan', 'alex', 'jacob']
  258. self.assertEqual(r.content, b'idanalexjacob')
  259. r = HttpResponse()
  260. r.content = [1, 2, 3]
  261. self.assertEqual(r.content, b'123')
  262. #test retrieval explicitly using iter and odd inputs
  263. r = HttpResponse()
  264. r.content = ['1', '2', 3, '\u079e']
  265. my_iter = r.__iter__()
  266. result = list(my_iter)
  267. #'\xde\x9e' == unichr(1950).encode('utf-8')
  268. self.assertEqual(result, [b'1', b'2', b'3', b'\xde\x9e'])
  269. self.assertEqual(r.content, b'123\xde\x9e')
  270. #with Content-Encoding header
  271. r = HttpResponse([1,1,2,4,8])
  272. r['Content-Encoding'] = 'winning'
  273. self.assertEqual(r.content, b'11248')
  274. r.content = ['\u079e',]
  275. self.assertRaises(UnicodeEncodeError,
  276. getattr, r, 'content')
  277. def test_file_interface(self):
  278. r = HttpResponse()
  279. r.write(b"hello")
  280. self.assertEqual(r.tell(), 5)
  281. r.write("привет")
  282. self.assertEqual(r.tell(), 17)
  283. r = HttpResponse(['abc'])
  284. self.assertRaises(Exception, r.write, 'def')
  285. def test_unsafe_redirect(self):
  286. bad_urls = [
  287. 'data:text/html,<script>window.alert("xss")</script>',
  288. 'mailto:test@example.com',
  289. 'file:///etc/passwd',
  290. ]
  291. for url in bad_urls:
  292. self.assertRaises(SuspiciousOperation,
  293. HttpResponseRedirect, url)
  294. self.assertRaises(SuspiciousOperation,
  295. HttpResponsePermanentRedirect, url)
  296. class HttpResponseSubclassesTests(TestCase):
  297. def test_redirect(self):
  298. response = HttpResponseRedirect('/redirected/')
  299. self.assertEqual(response.status_code, 302)
  300. # Test that standard HttpResponse init args can be used
  301. response = HttpResponseRedirect('/redirected/',
  302. content='The resource has temporarily moved',
  303. content_type='text/html')
  304. self.assertContains(response, 'The resource has temporarily moved', status_code=302)
  305. def test_not_modified(self):
  306. response = HttpResponseNotModified()
  307. self.assertEqual(response.status_code, 304)
  308. # 304 responses should not have content/content-type
  309. with self.assertRaises(AttributeError):
  310. response.content = "Hello dear"
  311. self.assertNotIn('content-type', response)
  312. def test_not_allowed(self):
  313. response = HttpResponseNotAllowed(['GET'])
  314. self.assertEqual(response.status_code, 405)
  315. # Test that standard HttpResponse init args can be used
  316. response = HttpResponseNotAllowed(['GET'],
  317. content='Only the GET method is allowed',
  318. content_type='text/html')
  319. self.assertContains(response, 'Only the GET method is allowed', status_code=405)
  320. class StreamingHttpResponseTests(TestCase):
  321. def test_streaming_response(self):
  322. r = StreamingHttpResponse(iter(['hello', 'world']))
  323. # iterating over the response itself yields bytestring chunks.
  324. chunks = list(r)
  325. self.assertEqual(chunks, [b'hello', b'world'])
  326. for chunk in chunks:
  327. self.assertIsInstance(chunk, six.binary_type)
  328. # and the response can only be iterated once.
  329. self.assertEqual(list(r), [])
  330. # even when a sequence that can be iterated many times, like a list,
  331. # is given as content.
  332. r = StreamingHttpResponse(['abc', 'def'])
  333. self.assertEqual(list(r), [b'abc', b'def'])
  334. self.assertEqual(list(r), [])
  335. # streaming responses don't have a `content` attribute.
  336. self.assertFalse(hasattr(r, 'content'))
  337. # and you can't accidentally assign to a `content` attribute.
  338. with self.assertRaises(AttributeError):
  339. r.content = 'xyz'
  340. # but they do have a `streaming_content` attribute.
  341. self.assertTrue(hasattr(r, 'streaming_content'))
  342. # that exists so we can check if a response is streaming, and wrap or
  343. # replace the content iterator.
  344. r.streaming_content = iter(['abc', 'def'])
  345. r.streaming_content = (chunk.upper() for chunk in r.streaming_content)
  346. self.assertEqual(list(r), [b'ABC', b'DEF'])
  347. # coercing a streaming response to bytes doesn't return a complete HTTP
  348. # message like a regular response does. it only gives us the headers.
  349. r = StreamingHttpResponse(iter(['hello', 'world']))
  350. self.assertEqual(
  351. six.binary_type(r), b'Content-Type: text/html; charset=utf-8')
  352. # and this won't consume its content.
  353. self.assertEqual(list(r), [b'hello', b'world'])
  354. # additional content cannot be written to the response.
  355. r = StreamingHttpResponse(iter(['hello', 'world']))
  356. with self.assertRaises(Exception):
  357. r.write('!')
  358. # and we can't tell the current position.
  359. with self.assertRaises(Exception):
  360. r.tell()
  361. class FileCloseTests(TestCase):
  362. def test_response(self):
  363. filename = os.path.join(os.path.dirname(__file__), 'abc.txt')
  364. # file isn't closed until we close the response.
  365. file1 = open(filename)
  366. r = HttpResponse(file1)
  367. self.assertFalse(file1.closed)
  368. r.close()
  369. self.assertTrue(file1.closed)
  370. # don't automatically close file when we finish iterating the response.
  371. file1 = open(filename)
  372. r = HttpResponse(file1)
  373. self.assertFalse(file1.closed)
  374. list(r)
  375. self.assertFalse(file1.closed)
  376. r.close()
  377. self.assertTrue(file1.closed)
  378. # when multiple file are assigned as content, make sure they are all
  379. # closed with the response.
  380. file1 = open(filename)
  381. file2 = open(filename)
  382. r = HttpResponse(file1)
  383. r.content = file2
  384. self.assertFalse(file1.closed)
  385. self.assertFalse(file2.closed)
  386. r.close()
  387. self.assertTrue(file1.closed)
  388. self.assertTrue(file2.closed)
  389. def test_streaming_response(self):
  390. filename = os.path.join(os.path.dirname(__file__), 'abc.txt')
  391. # file isn't closed until we close the response.
  392. file1 = open(filename)
  393. r = StreamingHttpResponse(file1)
  394. self.assertFalse(file1.closed)
  395. r.close()
  396. self.assertTrue(file1.closed)
  397. # when multiple file are assigned as content, make sure they are all
  398. # closed with the response.
  399. file1 = open(filename)
  400. file2 = open(filename)
  401. r = StreamingHttpResponse(file1)
  402. r.streaming_content = file2
  403. self.assertFalse(file1.closed)
  404. self.assertFalse(file2.closed)
  405. r.close()
  406. self.assertTrue(file1.closed)
  407. self.assertTrue(file2.closed)
  408. class CookieTests(unittest.TestCase):
  409. def test_encode(self):
  410. """
  411. Test that we don't output tricky characters in encoded value
  412. """
  413. c = SimpleCookie()
  414. c['test'] = "An,awkward;value"
  415. self.assertTrue(";" not in c.output().rstrip(';')) # IE compat
  416. self.assertTrue("," not in c.output().rstrip(';')) # Safari compat
  417. def test_decode(self):
  418. """
  419. Test that we can still preserve semi-colons and commas
  420. """
  421. c = SimpleCookie()
  422. c['test'] = "An,awkward;value"
  423. c2 = SimpleCookie()
  424. c2.load(c.output())
  425. self.assertEqual(c['test'].value, c2['test'].value)
  426. def test_decode_2(self):
  427. """
  428. Test that we haven't broken normal encoding
  429. """
  430. c = SimpleCookie()
  431. c['test'] = b"\xf0"
  432. c2 = SimpleCookie()
  433. c2.load(c.output())
  434. self.assertEqual(c['test'].value, c2['test'].value)
  435. def test_nonstandard_keys(self):
  436. """
  437. Test that a single non-standard cookie name doesn't affect all cookies. Ticket #13007.
  438. """
  439. self.assertTrue('good_cookie' in parse_cookie('good_cookie=yes;bad:cookie=yes').keys())
  440. def test_repeated_nonstandard_keys(self):
  441. """
  442. Test that a repeated non-standard name doesn't affect all cookies. Ticket #15852
  443. """
  444. self.assertTrue('good_cookie' in parse_cookie('a,=b; a,=c; good_cookie=yes').keys())
  445. def test_httponly_after_load(self):
  446. """
  447. Test that we can use httponly attribute on cookies that we load
  448. """
  449. c = SimpleCookie()
  450. c.load("name=val")
  451. c['name']['httponly'] = True
  452. self.assertTrue(c['name']['httponly'])