test_http_cookies.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. # Simple test suite for http/cookies.py
  2. import copy
  3. import unittest
  4. import doctest
  5. from http import cookies
  6. import pickle
  7. class CookieTests(unittest.TestCase):
  8. def test_basic(self):
  9. cases = [
  10. {'data': 'chips=ahoy; vienna=finger',
  11. 'dict': {'chips':'ahoy', 'vienna':'finger'},
  12. 'repr': "<SimpleCookie: chips='ahoy' vienna='finger'>",
  13. 'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'},
  14. {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"',
  15. 'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'},
  16. 'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=\\n;'>''',
  17. 'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'},
  18. # Check illegal cookies that have an '=' char in an unquoted value
  19. {'data': 'keebler=E=mc2',
  20. 'dict': {'keebler' : 'E=mc2'},
  21. 'repr': "<SimpleCookie: keebler='E=mc2'>",
  22. 'output': 'Set-Cookie: keebler=E=mc2'},
  23. # Cookies with ':' character in their name. Though not mentioned in
  24. # RFC, servers / browsers allow it.
  25. {'data': 'key:term=value:term',
  26. 'dict': {'key:term' : 'value:term'},
  27. 'repr': "<SimpleCookie: key:term='value:term'>",
  28. 'output': 'Set-Cookie: key:term=value:term'},
  29. # issue22931 - Adding '[' and ']' as valid characters in cookie
  30. # values as defined in RFC 6265
  31. {
  32. 'data': 'a=b; c=[; d=r; f=h',
  33. 'dict': {'a':'b', 'c':'[', 'd':'r', 'f':'h'},
  34. 'repr': "<SimpleCookie: a='b' c='[' d='r' f='h'>",
  35. 'output': '\n'.join((
  36. 'Set-Cookie: a=b',
  37. 'Set-Cookie: c=[',
  38. 'Set-Cookie: d=r',
  39. 'Set-Cookie: f=h'
  40. ))
  41. }
  42. ]
  43. for case in cases:
  44. C = cookies.SimpleCookie()
  45. C.load(case['data'])
  46. self.assertEqual(repr(C), case['repr'])
  47. self.assertEqual(C.output(sep='\n'), case['output'])
  48. for k, v in sorted(case['dict'].items()):
  49. self.assertEqual(C[k].value, v)
  50. def test_load(self):
  51. C = cookies.SimpleCookie()
  52. C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme')
  53. self.assertEqual(C['Customer'].value, 'WILE_E_COYOTE')
  54. self.assertEqual(C['Customer']['version'], '1')
  55. self.assertEqual(C['Customer']['path'], '/acme')
  56. self.assertEqual(C.output(['path']),
  57. 'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
  58. self.assertEqual(C.js_output(), r"""
  59. <script type="text/javascript">
  60. <!-- begin hiding
  61. document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
  62. // end hiding -->
  63. </script>
  64. """)
  65. self.assertEqual(C.js_output(['path']), r"""
  66. <script type="text/javascript">
  67. <!-- begin hiding
  68. document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
  69. // end hiding -->
  70. </script>
  71. """)
  72. def test_extended_encode(self):
  73. # Issue 9824: some browsers don't follow the standard; we now
  74. # encode , and ; to keep them from tripping up.
  75. C = cookies.SimpleCookie()
  76. C['val'] = "some,funky;stuff"
  77. self.assertEqual(C.output(['val']),
  78. 'Set-Cookie: val="some\\054funky\\073stuff"')
  79. def test_special_attrs(self):
  80. # 'expires'
  81. C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
  82. C['Customer']['expires'] = 0
  83. # can't test exact output, it always depends on current date/time
  84. self.assertTrue(C.output().endswith('GMT'))
  85. # loading 'expires'
  86. C = cookies.SimpleCookie()
  87. C.load('Customer="W"; expires=Wed, 01 Jan 2010 00:00:00 GMT')
  88. self.assertEqual(C['Customer']['expires'],
  89. 'Wed, 01 Jan 2010 00:00:00 GMT')
  90. C = cookies.SimpleCookie()
  91. C.load('Customer="W"; expires=Wed, 01 Jan 98 00:00:00 GMT')
  92. self.assertEqual(C['Customer']['expires'],
  93. 'Wed, 01 Jan 98 00:00:00 GMT')
  94. # 'max-age'
  95. C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
  96. C['Customer']['max-age'] = 10
  97. self.assertEqual(C.output(),
  98. 'Set-Cookie: Customer="WILE_E_COYOTE"; Max-Age=10')
  99. def test_set_secure_httponly_attrs(self):
  100. C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
  101. C['Customer']['secure'] = True
  102. C['Customer']['httponly'] = True
  103. self.assertEqual(C.output(),
  104. 'Set-Cookie: Customer="WILE_E_COYOTE"; HttpOnly; Secure')
  105. def test_samesite_attrs(self):
  106. samesite_values = ['Strict', 'Lax', 'strict', 'lax']
  107. for val in samesite_values:
  108. with self.subTest(val=val):
  109. C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
  110. C['Customer']['samesite'] = val
  111. self.assertEqual(C.output(),
  112. 'Set-Cookie: Customer="WILE_E_COYOTE"; SameSite=%s' % val)
  113. C = cookies.SimpleCookie()
  114. C.load('Customer="WILL_E_COYOTE"; SameSite=%s' % val)
  115. self.assertEqual(C['Customer']['samesite'], val)
  116. def test_secure_httponly_false_if_not_present(self):
  117. C = cookies.SimpleCookie()
  118. C.load('eggs=scrambled; Path=/bacon')
  119. self.assertFalse(C['eggs']['httponly'])
  120. self.assertFalse(C['eggs']['secure'])
  121. def test_secure_httponly_true_if_present(self):
  122. # Issue 16611
  123. C = cookies.SimpleCookie()
  124. C.load('eggs=scrambled; httponly; secure; Path=/bacon')
  125. self.assertTrue(C['eggs']['httponly'])
  126. self.assertTrue(C['eggs']['secure'])
  127. def test_secure_httponly_true_if_have_value(self):
  128. # This isn't really valid, but demonstrates what the current code
  129. # is expected to do in this case.
  130. C = cookies.SimpleCookie()
  131. C.load('eggs=scrambled; httponly=foo; secure=bar; Path=/bacon')
  132. self.assertTrue(C['eggs']['httponly'])
  133. self.assertTrue(C['eggs']['secure'])
  134. # Here is what it actually does; don't depend on this behavior. These
  135. # checks are testing backward compatibility for issue 16611.
  136. self.assertEqual(C['eggs']['httponly'], 'foo')
  137. self.assertEqual(C['eggs']['secure'], 'bar')
  138. def test_extra_spaces(self):
  139. C = cookies.SimpleCookie()
  140. C.load('eggs = scrambled ; secure ; path = bar ; foo=foo ')
  141. self.assertEqual(C.output(),
  142. 'Set-Cookie: eggs=scrambled; Path=bar; Secure\r\nSet-Cookie: foo=foo')
  143. def test_quoted_meta(self):
  144. # Try cookie with quoted meta-data
  145. C = cookies.SimpleCookie()
  146. C.load('Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"')
  147. self.assertEqual(C['Customer'].value, 'WILE_E_COYOTE')
  148. self.assertEqual(C['Customer']['version'], '1')
  149. self.assertEqual(C['Customer']['path'], '/acme')
  150. self.assertEqual(C.output(['path']),
  151. 'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
  152. self.assertEqual(C.js_output(), r"""
  153. <script type="text/javascript">
  154. <!-- begin hiding
  155. document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
  156. // end hiding -->
  157. </script>
  158. """)
  159. self.assertEqual(C.js_output(['path']), r"""
  160. <script type="text/javascript">
  161. <!-- begin hiding
  162. document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
  163. // end hiding -->
  164. </script>
  165. """)
  166. def test_invalid_cookies(self):
  167. # Accepting these could be a security issue
  168. C = cookies.SimpleCookie()
  169. for s in (']foo=x', '[foo=x', 'blah]foo=x', 'blah[foo=x',
  170. 'Set-Cookie: foo=bar', 'Set-Cookie: foo',
  171. 'foo=bar; baz', 'baz; foo=bar',
  172. 'secure;foo=bar', 'Version=1;foo=bar'):
  173. C.load(s)
  174. self.assertEqual(dict(C), {})
  175. self.assertEqual(C.output(), '')
  176. def test_pickle(self):
  177. rawdata = 'Customer="WILE_E_COYOTE"; Path=/acme; Version=1'
  178. expected_output = 'Set-Cookie: %s' % rawdata
  179. C = cookies.SimpleCookie()
  180. C.load(rawdata)
  181. self.assertEqual(C.output(), expected_output)
  182. for proto in range(pickle.HIGHEST_PROTOCOL + 1):
  183. with self.subTest(proto=proto):
  184. C1 = pickle.loads(pickle.dumps(C, protocol=proto))
  185. self.assertEqual(C1.output(), expected_output)
  186. def test_illegal_chars(self):
  187. rawdata = "a=b; c,d=e"
  188. C = cookies.SimpleCookie()
  189. with self.assertRaises(cookies.CookieError):
  190. C.load(rawdata)
  191. def test_comment_quoting(self):
  192. c = cookies.SimpleCookie()
  193. c['foo'] = '\N{COPYRIGHT SIGN}'
  194. self.assertEqual(str(c['foo']), 'Set-Cookie: foo="\\251"')
  195. c['foo']['comment'] = 'comment \N{COPYRIGHT SIGN}'
  196. self.assertEqual(
  197. str(c['foo']),
  198. 'Set-Cookie: foo="\\251"; Comment="comment \\251"'
  199. )
  200. class MorselTests(unittest.TestCase):
  201. """Tests for the Morsel object."""
  202. def test_defaults(self):
  203. morsel = cookies.Morsel()
  204. self.assertIsNone(morsel.key)
  205. self.assertIsNone(morsel.value)
  206. self.assertIsNone(morsel.coded_value)
  207. self.assertEqual(morsel.keys(), cookies.Morsel._reserved.keys())
  208. for key, val in morsel.items():
  209. self.assertEqual(val, '', key)
  210. def test_reserved_keys(self):
  211. M = cookies.Morsel()
  212. # tests valid and invalid reserved keys for Morsels
  213. for i in M._reserved:
  214. # Test that all valid keys are reported as reserved and set them
  215. self.assertTrue(M.isReservedKey(i))
  216. M[i] = '%s_value' % i
  217. for i in M._reserved:
  218. # Test that valid key values come out fine
  219. self.assertEqual(M[i], '%s_value' % i)
  220. for i in "the holy hand grenade".split():
  221. # Test that invalid keys raise CookieError
  222. self.assertRaises(cookies.CookieError,
  223. M.__setitem__, i, '%s_value' % i)
  224. def test_setter(self):
  225. M = cookies.Morsel()
  226. # tests the .set method to set keys and their values
  227. for i in M._reserved:
  228. # Makes sure that all reserved keys can't be set this way
  229. self.assertRaises(cookies.CookieError,
  230. M.set, i, '%s_value' % i, '%s_value' % i)
  231. for i in "thou cast _the- !holy! ^hand| +*grenade~".split():
  232. # Try typical use case. Setting decent values.
  233. # Check output and js_output.
  234. M['path'] = '/foo' # Try a reserved key as well
  235. M.set(i, "%s_val" % i, "%s_coded_val" % i)
  236. self.assertEqual(M.key, i)
  237. self.assertEqual(M.value, "%s_val" % i)
  238. self.assertEqual(M.coded_value, "%s_coded_val" % i)
  239. self.assertEqual(
  240. M.output(),
  241. "Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i))
  242. expected_js_output = """
  243. <script type="text/javascript">
  244. <!-- begin hiding
  245. document.cookie = "%s=%s; Path=/foo";
  246. // end hiding -->
  247. </script>
  248. """ % (i, "%s_coded_val" % i)
  249. self.assertEqual(M.js_output(), expected_js_output)
  250. for i in ["foo bar", "foo@bar"]:
  251. # Try some illegal characters
  252. self.assertRaises(cookies.CookieError,
  253. M.set, i, '%s_value' % i, '%s_value' % i)
  254. def test_set_properties(self):
  255. morsel = cookies.Morsel()
  256. with self.assertRaises(AttributeError):
  257. morsel.key = ''
  258. with self.assertRaises(AttributeError):
  259. morsel.value = ''
  260. with self.assertRaises(AttributeError):
  261. morsel.coded_value = ''
  262. def test_eq(self):
  263. base_case = ('key', 'value', '"value"')
  264. attribs = {
  265. 'path': '/',
  266. 'comment': 'foo',
  267. 'domain': 'example.com',
  268. 'version': 2,
  269. }
  270. morsel_a = cookies.Morsel()
  271. morsel_a.update(attribs)
  272. morsel_a.set(*base_case)
  273. morsel_b = cookies.Morsel()
  274. morsel_b.update(attribs)
  275. morsel_b.set(*base_case)
  276. self.assertTrue(morsel_a == morsel_b)
  277. self.assertFalse(morsel_a != morsel_b)
  278. cases = (
  279. ('key', 'value', 'mismatch'),
  280. ('key', 'mismatch', '"value"'),
  281. ('mismatch', 'value', '"value"'),
  282. )
  283. for case_b in cases:
  284. with self.subTest(case_b):
  285. morsel_b = cookies.Morsel()
  286. morsel_b.update(attribs)
  287. morsel_b.set(*case_b)
  288. self.assertFalse(morsel_a == morsel_b)
  289. self.assertTrue(morsel_a != morsel_b)
  290. morsel_b = cookies.Morsel()
  291. morsel_b.update(attribs)
  292. morsel_b.set(*base_case)
  293. morsel_b['comment'] = 'bar'
  294. self.assertFalse(morsel_a == morsel_b)
  295. self.assertTrue(morsel_a != morsel_b)
  296. # test mismatched types
  297. self.assertFalse(cookies.Morsel() == 1)
  298. self.assertTrue(cookies.Morsel() != 1)
  299. self.assertFalse(cookies.Morsel() == '')
  300. self.assertTrue(cookies.Morsel() != '')
  301. items = list(cookies.Morsel().items())
  302. self.assertFalse(cookies.Morsel() == items)
  303. self.assertTrue(cookies.Morsel() != items)
  304. # morsel/dict
  305. morsel = cookies.Morsel()
  306. morsel.set(*base_case)
  307. morsel.update(attribs)
  308. self.assertTrue(morsel == dict(morsel))
  309. self.assertFalse(morsel != dict(morsel))
  310. def test_copy(self):
  311. morsel_a = cookies.Morsel()
  312. morsel_a.set('foo', 'bar', 'baz')
  313. morsel_a.update({
  314. 'version': 2,
  315. 'comment': 'foo',
  316. })
  317. morsel_b = morsel_a.copy()
  318. self.assertIsInstance(morsel_b, cookies.Morsel)
  319. self.assertIsNot(morsel_a, morsel_b)
  320. self.assertEqual(morsel_a, morsel_b)
  321. morsel_b = copy.copy(morsel_a)
  322. self.assertIsInstance(morsel_b, cookies.Morsel)
  323. self.assertIsNot(morsel_a, morsel_b)
  324. self.assertEqual(morsel_a, morsel_b)
  325. def test_setitem(self):
  326. morsel = cookies.Morsel()
  327. morsel['expires'] = 0
  328. self.assertEqual(morsel['expires'], 0)
  329. morsel['Version'] = 2
  330. self.assertEqual(morsel['version'], 2)
  331. morsel['DOMAIN'] = 'example.com'
  332. self.assertEqual(morsel['domain'], 'example.com')
  333. with self.assertRaises(cookies.CookieError):
  334. morsel['invalid'] = 'value'
  335. self.assertNotIn('invalid', morsel)
  336. def test_setdefault(self):
  337. morsel = cookies.Morsel()
  338. morsel.update({
  339. 'domain': 'example.com',
  340. 'version': 2,
  341. })
  342. # this shouldn't override the default value
  343. self.assertEqual(morsel.setdefault('expires', 'value'), '')
  344. self.assertEqual(morsel['expires'], '')
  345. self.assertEqual(morsel.setdefault('Version', 1), 2)
  346. self.assertEqual(morsel['version'], 2)
  347. self.assertEqual(morsel.setdefault('DOMAIN', 'value'), 'example.com')
  348. self.assertEqual(morsel['domain'], 'example.com')
  349. with self.assertRaises(cookies.CookieError):
  350. morsel.setdefault('invalid', 'value')
  351. self.assertNotIn('invalid', morsel)
  352. def test_update(self):
  353. attribs = {'expires': 1, 'Version': 2, 'DOMAIN': 'example.com'}
  354. # test dict update
  355. morsel = cookies.Morsel()
  356. morsel.update(attribs)
  357. self.assertEqual(morsel['expires'], 1)
  358. self.assertEqual(morsel['version'], 2)
  359. self.assertEqual(morsel['domain'], 'example.com')
  360. # test iterable update
  361. morsel = cookies.Morsel()
  362. morsel.update(list(attribs.items()))
  363. self.assertEqual(morsel['expires'], 1)
  364. self.assertEqual(morsel['version'], 2)
  365. self.assertEqual(morsel['domain'], 'example.com')
  366. # test iterator update
  367. morsel = cookies.Morsel()
  368. morsel.update((k, v) for k, v in attribs.items())
  369. self.assertEqual(morsel['expires'], 1)
  370. self.assertEqual(morsel['version'], 2)
  371. self.assertEqual(morsel['domain'], 'example.com')
  372. with self.assertRaises(cookies.CookieError):
  373. morsel.update({'invalid': 'value'})
  374. self.assertNotIn('invalid', morsel)
  375. self.assertRaises(TypeError, morsel.update)
  376. self.assertRaises(TypeError, morsel.update, 0)
  377. def test_pickle(self):
  378. morsel_a = cookies.Morsel()
  379. morsel_a.set('foo', 'bar', 'baz')
  380. morsel_a.update({
  381. 'version': 2,
  382. 'comment': 'foo',
  383. })
  384. for proto in range(pickle.HIGHEST_PROTOCOL + 1):
  385. with self.subTest(proto=proto):
  386. morsel_b = pickle.loads(pickle.dumps(morsel_a, proto))
  387. self.assertIsInstance(morsel_b, cookies.Morsel)
  388. self.assertEqual(morsel_b, morsel_a)
  389. self.assertEqual(str(morsel_b), str(morsel_a))
  390. def test_repr(self):
  391. morsel = cookies.Morsel()
  392. self.assertEqual(repr(morsel), '<Morsel: None=None>')
  393. self.assertEqual(str(morsel), 'Set-Cookie: None=None')
  394. morsel.set('key', 'val', 'coded_val')
  395. self.assertEqual(repr(morsel), '<Morsel: key=coded_val>')
  396. self.assertEqual(str(morsel), 'Set-Cookie: key=coded_val')
  397. morsel.update({
  398. 'path': '/',
  399. 'comment': 'foo',
  400. 'domain': 'example.com',
  401. 'max-age': 0,
  402. 'secure': 0,
  403. 'version': 1,
  404. })
  405. self.assertEqual(repr(morsel),
  406. '<Morsel: key=coded_val; Comment=foo; Domain=example.com; '
  407. 'Max-Age=0; Path=/; Version=1>')
  408. self.assertEqual(str(morsel),
  409. 'Set-Cookie: key=coded_val; Comment=foo; Domain=example.com; '
  410. 'Max-Age=0; Path=/; Version=1')
  411. morsel['secure'] = True
  412. morsel['httponly'] = 1
  413. self.assertEqual(repr(morsel),
  414. '<Morsel: key=coded_val; Comment=foo; Domain=example.com; '
  415. 'HttpOnly; Max-Age=0; Path=/; Secure; Version=1>')
  416. self.assertEqual(str(morsel),
  417. 'Set-Cookie: key=coded_val; Comment=foo; Domain=example.com; '
  418. 'HttpOnly; Max-Age=0; Path=/; Secure; Version=1')
  419. morsel = cookies.Morsel()
  420. morsel.set('key', 'val', 'coded_val')
  421. morsel['expires'] = 0
  422. self.assertRegex(repr(morsel),
  423. r'<Morsel: key=coded_val; '
  424. r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+>')
  425. self.assertRegex(str(morsel),
  426. r'Set-Cookie: key=coded_val; '
  427. r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+')
  428. def load_tests(loader, tests, pattern):
  429. tests.addTest(doctest.DocTestSuite(cookies))
  430. return tests
  431. if __name__ == '__main__':
  432. unittest.main()