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