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