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