1"""Tests for http/cookiejar.py.""" 2 3import os 4import stat 5import sys 6import re 7import test.support 8from test.support import os_helper 9from test.support import warnings_helper 10import time 11import unittest 12import urllib.request 13import pathlib 14 15from http.cookiejar import (time2isoz, http2time, iso2time, time2netscape, 16 parse_ns_headers, join_header_words, split_header_words, Cookie, 17 CookieJar, DefaultCookiePolicy, LWPCookieJar, MozillaCookieJar, 18 LoadError, lwp_cookie_str, DEFAULT_HTTP_PORT, escape_path, 19 reach, is_HDN, domain_match, user_domain_match, request_path, 20 request_port, request_host) 21 22mswindows = (sys.platform == "win32") 23 24class DateTimeTests(unittest.TestCase): 25 26 def test_time2isoz(self): 27 base = 1019227000 28 day = 24*3600 29 self.assertEqual(time2isoz(base), "2002-04-19 14:36:40Z") 30 self.assertEqual(time2isoz(base+day), "2002-04-20 14:36:40Z") 31 self.assertEqual(time2isoz(base+2*day), "2002-04-21 14:36:40Z") 32 self.assertEqual(time2isoz(base+3*day), "2002-04-22 14:36:40Z") 33 34 az = time2isoz() 35 bz = time2isoz(500000) 36 for text in (az, bz): 37 self.assertRegex(text, r"^\d{4}-\d\d-\d\d \d\d:\d\d:\d\dZ$", 38 "bad time2isoz format: %s %s" % (az, bz)) 39 40 def test_time2netscape(self): 41 base = 1019227000 42 day = 24*3600 43 self.assertEqual(time2netscape(base), "Fri, 19-Apr-2002 14:36:40 GMT") 44 self.assertEqual(time2netscape(base+day), 45 "Sat, 20-Apr-2002 14:36:40 GMT") 46 47 self.assertEqual(time2netscape(base+2*day), 48 "Sun, 21-Apr-2002 14:36:40 GMT") 49 50 self.assertEqual(time2netscape(base+3*day), 51 "Mon, 22-Apr-2002 14:36:40 GMT") 52 53 az = time2netscape() 54 bz = time2netscape(500000) 55 for text in (az, bz): 56 # Format "%s, %02d-%s-%04d %02d:%02d:%02d GMT" 57 self.assertRegex( 58 text, 59 r"[a-zA-Z]{3}, \d{2}-[a-zA-Z]{3}-\d{4} \d{2}:\d{2}:\d{2} GMT$", 60 "bad time2netscape format: %s %s" % (az, bz)) 61 62 def test_http2time(self): 63 def parse_date(text): 64 return time.gmtime(http2time(text))[:6] 65 66 self.assertEqual(parse_date("01 Jan 2001"), (2001, 1, 1, 0, 0, 0.0)) 67 68 # this test will break around year 2070 69 self.assertEqual(parse_date("03-Feb-20"), (2020, 2, 3, 0, 0, 0.0)) 70 71 # this test will break around year 2048 72 self.assertEqual(parse_date("03-Feb-98"), (1998, 2, 3, 0, 0, 0.0)) 73 74 def test_http2time_formats(self): 75 # test http2time for supported dates. Test cases with 2 digit year 76 # will probably break in year 2044. 77 tests = [ 78 'Thu, 03 Feb 1994 00:00:00 GMT', # proposed new HTTP format 79 'Thursday, 03-Feb-94 00:00:00 GMT', # old rfc850 HTTP format 80 'Thursday, 03-Feb-1994 00:00:00 GMT', # broken rfc850 HTTP format 81 82 '03 Feb 1994 00:00:00 GMT', # HTTP format (no weekday) 83 '03-Feb-94 00:00:00 GMT', # old rfc850 (no weekday) 84 '03-Feb-1994 00:00:00 GMT', # broken rfc850 (no weekday) 85 '03-Feb-1994 00:00 GMT', # broken rfc850 (no weekday, no seconds) 86 '03-Feb-1994 00:00', # broken rfc850 (no weekday, no seconds, no tz) 87 '02-Feb-1994 24:00', # broken rfc850 (no weekday, no seconds, 88 # no tz) using hour 24 with yesterday date 89 90 '03-Feb-94', # old rfc850 HTTP format (no weekday, no time) 91 '03-Feb-1994', # broken rfc850 HTTP format (no weekday, no time) 92 '03 Feb 1994', # proposed new HTTP format (no weekday, no time) 93 94 # A few tests with extra space at various places 95 ' 03 Feb 1994 0:00 ', 96 ' 03-Feb-1994 ', 97 ] 98 99 test_t = 760233600 # assume broken POSIX counting of seconds 100 result = time2isoz(test_t) 101 expected = "1994-02-03 00:00:00Z" 102 self.assertEqual(result, expected, 103 "%s => '%s' (%s)" % (test_t, result, expected)) 104 105 for s in tests: 106 self.assertEqual(http2time(s), test_t, s) 107 self.assertEqual(http2time(s.lower()), test_t, s.lower()) 108 self.assertEqual(http2time(s.upper()), test_t, s.upper()) 109 110 def test_http2time_garbage(self): 111 for test in [ 112 '', 113 'Garbage', 114 'Mandag 16. September 1996', 115 '01-00-1980', 116 '01-13-1980', 117 '00-01-1980', 118 '32-01-1980', 119 '01-01-1980 25:00:00', 120 '01-01-1980 00:61:00', 121 '01-01-1980 00:00:62', 122 '08-Oct-3697739', 123 '08-01-3697739', 124 '09 Feb 19942632 22:23:32 GMT', 125 'Wed, 09 Feb 1994834 22:23:32 GMT', 126 ]: 127 self.assertIsNone(http2time(test), 128 "http2time(%s) is not None\n" 129 "http2time(test) %s" % (test, http2time(test))) 130 131 def test_http2time_redos_regression_actually_completes(self): 132 # LOOSE_HTTP_DATE_RE was vulnerable to malicious input which caused catastrophic backtracking (REDoS). 133 # If we regress to cubic complexity, this test will take a very long time to succeed. 134 # If fixed, it should complete within a fraction of a second. 135 http2time("01 Jan 1970{}00:00:00 GMT!".format(" " * 10 ** 5)) 136 http2time("01 Jan 1970 00:00:00{}GMT!".format(" " * 10 ** 5)) 137 138 def test_iso2time(self): 139 def parse_date(text): 140 return time.gmtime(iso2time(text))[:6] 141 142 # ISO 8601 compact format 143 self.assertEqual(parse_date("19940203T141529Z"), 144 (1994, 2, 3, 14, 15, 29)) 145 146 # ISO 8601 with time behind UTC 147 self.assertEqual(parse_date("1994-02-03 07:15:29 -0700"), 148 (1994, 2, 3, 14, 15, 29)) 149 150 # ISO 8601 with time ahead of UTC 151 self.assertEqual(parse_date("1994-02-03 19:45:29 +0530"), 152 (1994, 2, 3, 14, 15, 29)) 153 154 def test_iso2time_formats(self): 155 # test iso2time for supported dates. 156 tests = [ 157 '1994-02-03 00:00:00 -0000', # ISO 8601 format 158 '1994-02-03 00:00:00 +0000', # ISO 8601 format 159 '1994-02-03 00:00:00', # zone is optional 160 '1994-02-03', # only date 161 '1994-02-03T00:00:00', # Use T as separator 162 '19940203', # only date 163 '1994-02-02 24:00:00', # using hour-24 yesterday date 164 '19940203T000000Z', # ISO 8601 compact format 165 166 # A few tests with extra space at various places 167 ' 1994-02-03 ', 168 ' 1994-02-03T00:00:00 ', 169 ] 170 171 test_t = 760233600 # assume broken POSIX counting of seconds 172 for s in tests: 173 self.assertEqual(iso2time(s), test_t, s) 174 self.assertEqual(iso2time(s.lower()), test_t, s.lower()) 175 self.assertEqual(iso2time(s.upper()), test_t, s.upper()) 176 177 def test_iso2time_garbage(self): 178 for test in [ 179 '', 180 'Garbage', 181 'Thursday, 03-Feb-94 00:00:00 GMT', 182 '1980-00-01', 183 '1980-13-01', 184 '1980-01-00', 185 '1980-01-32', 186 '1980-01-01 25:00:00', 187 '1980-01-01 00:61:00', 188 '01-01-1980 00:00:62', 189 '01-01-1980T00:00:62', 190 '19800101T250000Z', 191 ]: 192 self.assertIsNone(iso2time(test), 193 "iso2time(%r)" % test) 194 195 def test_iso2time_performance_regression(self): 196 # If ISO_DATE_RE regresses to quadratic complexity, this test will take a very long time to succeed. 197 # If fixed, it should complete within a fraction of a second. 198 iso2time('1994-02-03{}14:15:29 -0100!'.format(' '*10**6)) 199 iso2time('1994-02-03 14:15:29{}-0100!'.format(' '*10**6)) 200 201 202class HeaderTests(unittest.TestCase): 203 204 def test_parse_ns_headers(self): 205 # quotes should be stripped 206 expected = [[('foo', 'bar'), ('expires', 2209069412), ('version', '0')]] 207 for hdr in [ 208 'foo=bar; expires=01 Jan 2040 22:23:32 GMT', 209 'foo=bar; expires="01 Jan 2040 22:23:32 GMT"', 210 ]: 211 self.assertEqual(parse_ns_headers([hdr]), expected) 212 213 def test_parse_ns_headers_version(self): 214 215 # quotes should be stripped 216 expected = [[('foo', 'bar'), ('version', '1')]] 217 for hdr in [ 218 'foo=bar; version="1"', 219 'foo=bar; Version="1"', 220 ]: 221 self.assertEqual(parse_ns_headers([hdr]), expected) 222 223 def test_parse_ns_headers_special_names(self): 224 # names such as 'expires' are not special in first name=value pair 225 # of Set-Cookie: header 226 # Cookie with name 'expires' 227 hdr = 'expires=01 Jan 2040 22:23:32 GMT' 228 expected = [[("expires", "01 Jan 2040 22:23:32 GMT"), ("version", "0")]] 229 self.assertEqual(parse_ns_headers([hdr]), expected) 230 231 def test_join_header_words(self): 232 joined = join_header_words([[("foo", None), ("bar", "baz")]]) 233 self.assertEqual(joined, "foo; bar=baz") 234 235 self.assertEqual(join_header_words([[]]), "") 236 237 def test_split_header_words(self): 238 tests = [ 239 ("foo", [[("foo", None)]]), 240 ("foo=bar", [[("foo", "bar")]]), 241 (" foo ", [[("foo", None)]]), 242 (" foo= ", [[("foo", "")]]), 243 (" foo=", [[("foo", "")]]), 244 (" foo= ; ", [[("foo", "")]]), 245 (" foo= ; bar= baz ", [[("foo", ""), ("bar", "baz")]]), 246 ("foo=bar bar=baz", [[("foo", "bar"), ("bar", "baz")]]), 247 # doesn't really matter if this next fails, but it works ATM 248 ("foo= bar=baz", [[("foo", "bar=baz")]]), 249 ("foo=bar;bar=baz", [[("foo", "bar"), ("bar", "baz")]]), 250 ('foo bar baz', [[("foo", None), ("bar", None), ("baz", None)]]), 251 ("a, b, c", [[("a", None)], [("b", None)], [("c", None)]]), 252 (r'foo; bar=baz, spam=, foo="\,\;\"", bar= ', 253 [[("foo", None), ("bar", "baz")], 254 [("spam", "")], [("foo", ',;"')], [("bar", "")]]), 255 ] 256 257 for arg, expect in tests: 258 try: 259 result = split_header_words([arg]) 260 except: 261 import traceback, io 262 f = io.StringIO() 263 traceback.print_exc(None, f) 264 result = "(error -- traceback follows)\n\n%s" % f.getvalue() 265 self.assertEqual(result, expect, """ 266When parsing: '%s' 267Expected: '%s' 268Got: '%s' 269""" % (arg, expect, result)) 270 271 def test_roundtrip(self): 272 tests = [ 273 ("foo", "foo"), 274 ("foo=bar", "foo=bar"), 275 (" foo ", "foo"), 276 ("foo=", 'foo=""'), 277 ("foo=bar bar=baz", "foo=bar; bar=baz"), 278 ("foo=bar;bar=baz", "foo=bar; bar=baz"), 279 ('foo bar baz', "foo; bar; baz"), 280 (r'foo="\"" bar="\\"', r'foo="\""; bar="\\"'), 281 ('foo,,,bar', 'foo, bar'), 282 ('foo=bar,bar=baz', 'foo=bar, bar=baz'), 283 284 ('text/html; charset=iso-8859-1', 285 'text/html; charset="iso-8859-1"'), 286 287 ('foo="bar"; port="80,81"; discard, bar=baz', 288 'foo=bar; port="80,81"; discard, bar=baz'), 289 290 (r'Basic realm="\"foo\\\\bar\""', 291 r'Basic; realm="\"foo\\\\bar\""') 292 ] 293 294 for arg, expect in tests: 295 input = split_header_words([arg]) 296 res = join_header_words(input) 297 self.assertEqual(res, expect, """ 298When parsing: '%s' 299Expected: '%s' 300Got: '%s' 301Input was: '%s' 302""" % (arg, expect, res, input)) 303 304 305class FakeResponse: 306 def __init__(self, headers=[], url=None): 307 """ 308 headers: list of RFC822-style 'Key: value' strings 309 """ 310 import email 311 self._headers = email.message_from_string("\n".join(headers)) 312 self._url = url 313 def info(self): return self._headers 314 315def interact_2965(cookiejar, url, *set_cookie_hdrs): 316 return _interact(cookiejar, url, set_cookie_hdrs, "Set-Cookie2") 317 318def interact_netscape(cookiejar, url, *set_cookie_hdrs): 319 return _interact(cookiejar, url, set_cookie_hdrs, "Set-Cookie") 320 321def _interact(cookiejar, url, set_cookie_hdrs, hdr_name): 322 """Perform a single request / response cycle, returning Cookie: header.""" 323 req = urllib.request.Request(url) 324 cookiejar.add_cookie_header(req) 325 cookie_hdr = req.get_header("Cookie", "") 326 headers = [] 327 for hdr in set_cookie_hdrs: 328 headers.append("%s: %s" % (hdr_name, hdr)) 329 res = FakeResponse(headers, url) 330 cookiejar.extract_cookies(res, req) 331 return cookie_hdr 332 333 334class FileCookieJarTests(unittest.TestCase): 335 def test_constructor_with_str(self): 336 filename = os_helper.TESTFN 337 c = LWPCookieJar(filename) 338 self.assertEqual(c.filename, filename) 339 340 def test_constructor_with_path_like(self): 341 filename = pathlib.Path(os_helper.TESTFN) 342 c = LWPCookieJar(filename) 343 self.assertEqual(c.filename, os.fspath(filename)) 344 345 def test_constructor_with_none(self): 346 c = LWPCookieJar(None) 347 self.assertIsNone(c.filename) 348 349 def test_constructor_with_other_types(self): 350 class A: 351 pass 352 353 for type_ in (int, float, A): 354 with self.subTest(filename=type_): 355 with self.assertRaises(TypeError): 356 instance = type_() 357 c = LWPCookieJar(filename=instance) 358 359 def test_lwp_valueless_cookie(self): 360 # cookies with no value should be saved and loaded consistently 361 filename = os_helper.TESTFN 362 c = LWPCookieJar() 363 interact_netscape(c, "http://www.acme.com/", 'boo') 364 self.assertEqual(c._cookies["www.acme.com"]["/"]["boo"].value, None) 365 try: 366 c.save(filename, ignore_discard=True) 367 c = LWPCookieJar() 368 c.load(filename, ignore_discard=True) 369 finally: 370 os_helper.unlink(filename) 371 self.assertEqual(c._cookies["www.acme.com"]["/"]["boo"].value, None) 372 373 @unittest.skipIf(mswindows, "windows file permissions are incompatible with file modes") 374 @os_helper.skip_unless_working_chmod 375 def test_lwp_filepermissions(self): 376 # Cookie file should only be readable by the creator 377 filename = os_helper.TESTFN 378 c = LWPCookieJar() 379 interact_netscape(c, "http://www.acme.com/", 'boo') 380 try: 381 c.save(filename, ignore_discard=True) 382 st = os.stat(filename) 383 self.assertEqual(stat.S_IMODE(st.st_mode), 0o600) 384 finally: 385 os_helper.unlink(filename) 386 387 @unittest.skipIf(mswindows, "windows file permissions are incompatible with file modes") 388 @os_helper.skip_unless_working_chmod 389 def test_mozilla_filepermissions(self): 390 # Cookie file should only be readable by the creator 391 filename = os_helper.TESTFN 392 c = MozillaCookieJar() 393 interact_netscape(c, "http://www.acme.com/", 'boo') 394 try: 395 c.save(filename, ignore_discard=True) 396 st = os.stat(filename) 397 self.assertEqual(stat.S_IMODE(st.st_mode), 0o600) 398 finally: 399 os_helper.unlink(filename) 400 401 @unittest.skipIf(mswindows, "windows file permissions are incompatible with file modes") 402 @os_helper.skip_unless_working_chmod 403 def test_cookie_files_are_truncated(self): 404 filename = os_helper.TESTFN 405 for cookiejar_class in (LWPCookieJar, MozillaCookieJar): 406 c = cookiejar_class(filename) 407 408 req = urllib.request.Request("http://www.acme.com/") 409 headers = ["Set-Cookie: pll_lang=en; Max-Age=31536000; path=/"] 410 res = FakeResponse(headers, "http://www.acme.com/") 411 c.extract_cookies(res, req) 412 self.assertEqual(len(c), 1) 413 414 try: 415 # Save the first version with contents: 416 c.save() 417 # Now, clear cookies and re-save: 418 c.clear() 419 c.save() 420 # Check that file was truncated: 421 c.load() 422 finally: 423 os_helper.unlink(filename) 424 425 self.assertEqual(len(c), 0) 426 427 def test_bad_magic(self): 428 # OSErrors (eg. file doesn't exist) are allowed to propagate 429 filename = os_helper.TESTFN 430 for cookiejar_class in LWPCookieJar, MozillaCookieJar: 431 c = cookiejar_class() 432 try: 433 c.load(filename="for this test to work, a file with this " 434 "filename should not exist") 435 except OSError as exc: 436 # an OSError subclass (likely FileNotFoundError), but not 437 # LoadError 438 self.assertIsNot(exc.__class__, LoadError) 439 else: 440 self.fail("expected OSError for invalid filename") 441 # Invalid contents of cookies file (eg. bad magic string) 442 # causes a LoadError. 443 try: 444 with open(filename, "w") as f: 445 f.write("oops\n") 446 for cookiejar_class in LWPCookieJar, MozillaCookieJar: 447 c = cookiejar_class() 448 self.assertRaises(LoadError, c.load, filename) 449 finally: 450 os_helper.unlink(filename) 451 452class CookieTests(unittest.TestCase): 453 # XXX 454 # Get rid of string comparisons where not actually testing str / repr. 455 # .clear() etc. 456 # IP addresses like 50 (single number, no dot) and domain-matching 457 # functions (and is_HDN)? See draft RFC 2965 errata. 458 # Strictness switches 459 # is_third_party() 460 # unverifiability / third-party blocking 461 # Netscape cookies work the same as RFC 2965 with regard to port. 462 # Set-Cookie with negative max age. 463 # If turn RFC 2965 handling off, Set-Cookie2 cookies should not clobber 464 # Set-Cookie cookies. 465 # Cookie2 should be sent if *any* cookies are not V1 (ie. V0 OR V2 etc.). 466 # Cookies (V1 and V0) with no expiry date should be set to be discarded. 467 # RFC 2965 Quoting: 468 # Should accept unquoted cookie-attribute values? check errata draft. 469 # Which are required on the way in and out? 470 # Should always return quoted cookie-attribute values? 471 # Proper testing of when RFC 2965 clobbers Netscape (waiting for errata). 472 # Path-match on return (same for V0 and V1). 473 # RFC 2965 acceptance and returning rules 474 # Set-Cookie2 without version attribute is rejected. 475 476 # Netscape peculiarities list from Ronald Tschalar. 477 # The first two still need tests, the rest are covered. 478## - Quoting: only quotes around the expires value are recognized as such 479## (and yes, some folks quote the expires value); quotes around any other 480## value are treated as part of the value. 481## - White space: white space around names and values is ignored 482## - Default path: if no path parameter is given, the path defaults to the 483## path in the request-uri up to, but not including, the last '/'. Note 484## that this is entirely different from what the spec says. 485## - Commas and other delimiters: Netscape just parses until the next ';'. 486## This means it will allow commas etc inside values (and yes, both 487## commas and equals are commonly appear in the cookie value). This also 488## means that if you fold multiple Set-Cookie header fields into one, 489## comma-separated list, it'll be a headache to parse (at least my head 490## starts hurting every time I think of that code). 491## - Expires: You'll get all sorts of date formats in the expires, 492## including empty expires attributes ("expires="). Be as flexible as you 493## can, and certainly don't expect the weekday to be there; if you can't 494## parse it, just ignore it and pretend it's a session cookie. 495## - Domain-matching: Netscape uses the 2-dot rule for _all_ domains, not 496## just the 7 special TLD's listed in their spec. And folks rely on 497## that... 498 499 def test_domain_return_ok(self): 500 # test optimization: .domain_return_ok() should filter out most 501 # domains in the CookieJar before we try to access them (because that 502 # may require disk access -- in particular, with MSIECookieJar) 503 # This is only a rough check for performance reasons, so it's not too 504 # critical as long as it's sufficiently liberal. 505 pol = DefaultCookiePolicy() 506 for url, domain, ok in [ 507 ("http://foo.bar.com/", "blah.com", False), 508 ("http://foo.bar.com/", "rhubarb.blah.com", False), 509 ("http://foo.bar.com/", "rhubarb.foo.bar.com", False), 510 ("http://foo.bar.com/", ".foo.bar.com", True), 511 ("http://foo.bar.com/", "foo.bar.com", True), 512 ("http://foo.bar.com/", ".bar.com", True), 513 ("http://foo.bar.com/", "bar.com", True), 514 ("http://foo.bar.com/", "com", True), 515 ("http://foo.com/", "rhubarb.foo.com", False), 516 ("http://foo.com/", ".foo.com", True), 517 ("http://foo.com/", "foo.com", True), 518 ("http://foo.com/", "com", True), 519 ("http://foo/", "rhubarb.foo", False), 520 ("http://foo/", ".foo", True), 521 ("http://foo/", "foo", True), 522 ("http://foo/", "foo.local", True), 523 ("http://foo/", ".local", True), 524 ("http://barfoo.com", ".foo.com", False), 525 ("http://barfoo.com", "foo.com", False), 526 ]: 527 request = urllib.request.Request(url) 528 r = pol.domain_return_ok(domain, request) 529 if ok: self.assertTrue(r) 530 else: self.assertFalse(r) 531 532 def test_missing_value(self): 533 # missing = sign in Cookie: header is regarded by Mozilla as a missing 534 # name, and by http.cookiejar as a missing value 535 filename = os_helper.TESTFN 536 c = MozillaCookieJar(filename) 537 interact_netscape(c, "http://www.acme.com/", 'eggs') 538 interact_netscape(c, "http://www.acme.com/", '"spam"; path=/foo/') 539 cookie = c._cookies["www.acme.com"]["/"]["eggs"] 540 self.assertIsNone(cookie.value) 541 self.assertEqual(cookie.name, "eggs") 542 cookie = c._cookies["www.acme.com"]['/foo/']['"spam"'] 543 self.assertIsNone(cookie.value) 544 self.assertEqual(cookie.name, '"spam"') 545 self.assertEqual(lwp_cookie_str(cookie), ( 546 r'"spam"; path="/foo/"; domain="www.acme.com"; ' 547 'path_spec; discard; version=0')) 548 old_str = repr(c) 549 c.save(ignore_expires=True, ignore_discard=True) 550 try: 551 c = MozillaCookieJar(filename) 552 c.revert(ignore_expires=True, ignore_discard=True) 553 finally: 554 os_helper.unlink(c.filename) 555 # cookies unchanged apart from lost info re. whether path was specified 556 self.assertEqual( 557 repr(c), 558 re.sub("path_specified=%s" % True, "path_specified=%s" % False, 559 old_str) 560 ) 561 self.assertEqual(interact_netscape(c, "http://www.acme.com/foo/"), 562 '"spam"; eggs') 563 564 def test_rfc2109_handling(self): 565 # RFC 2109 cookies are handled as RFC 2965 or Netscape cookies, 566 # dependent on policy settings 567 for rfc2109_as_netscape, rfc2965, version in [ 568 # default according to rfc2965 if not explicitly specified 569 (None, False, 0), 570 (None, True, 1), 571 # explicit rfc2109_as_netscape 572 (False, False, None), # version None here means no cookie stored 573 (False, True, 1), 574 (True, False, 0), 575 (True, True, 0), 576 ]: 577 policy = DefaultCookiePolicy( 578 rfc2109_as_netscape=rfc2109_as_netscape, 579 rfc2965=rfc2965) 580 c = CookieJar(policy) 581 interact_netscape(c, "http://www.example.com/", "ni=ni; Version=1") 582 try: 583 cookie = c._cookies["www.example.com"]["/"]["ni"] 584 except KeyError: 585 self.assertIsNone(version) # didn't expect a stored cookie 586 else: 587 self.assertEqual(cookie.version, version) 588 # 2965 cookies are unaffected 589 interact_2965(c, "http://www.example.com/", 590 "foo=bar; Version=1") 591 if rfc2965: 592 cookie2965 = c._cookies["www.example.com"]["/"]["foo"] 593 self.assertEqual(cookie2965.version, 1) 594 595 def test_ns_parser(self): 596 c = CookieJar() 597 interact_netscape(c, "http://www.acme.com/", 598 'spam=eggs; DoMain=.acme.com; port; blArgh="feep"') 599 interact_netscape(c, "http://www.acme.com/", 'ni=ni; port=80,8080') 600 interact_netscape(c, "http://www.acme.com:80/", 'nini=ni') 601 interact_netscape(c, "http://www.acme.com:80/", 'foo=bar; expires=') 602 interact_netscape(c, "http://www.acme.com:80/", 'spam=eggs; ' 603 'expires="Foo Bar 25 33:22:11 3022"') 604 interact_netscape(c, 'http://www.acme.com/', 'fortytwo=') 605 interact_netscape(c, 'http://www.acme.com/', '=unladenswallow') 606 interact_netscape(c, 'http://www.acme.com/', 'holyhandgrenade') 607 608 cookie = c._cookies[".acme.com"]["/"]["spam"] 609 self.assertEqual(cookie.domain, ".acme.com") 610 self.assertTrue(cookie.domain_specified) 611 self.assertEqual(cookie.port, DEFAULT_HTTP_PORT) 612 self.assertFalse(cookie.port_specified) 613 # case is preserved 614 self.assertTrue(cookie.has_nonstandard_attr("blArgh")) 615 self.assertFalse(cookie.has_nonstandard_attr("blargh")) 616 617 cookie = c._cookies["www.acme.com"]["/"]["ni"] 618 self.assertEqual(cookie.domain, "www.acme.com") 619 self.assertFalse(cookie.domain_specified) 620 self.assertEqual(cookie.port, "80,8080") 621 self.assertTrue(cookie.port_specified) 622 623 cookie = c._cookies["www.acme.com"]["/"]["nini"] 624 self.assertIsNone(cookie.port) 625 self.assertFalse(cookie.port_specified) 626 627 # invalid expires should not cause cookie to be dropped 628 foo = c._cookies["www.acme.com"]["/"]["foo"] 629 spam = c._cookies["www.acme.com"]["/"]["foo"] 630 self.assertIsNone(foo.expires) 631 self.assertIsNone(spam.expires) 632 633 cookie = c._cookies['www.acme.com']['/']['fortytwo'] 634 self.assertIsNotNone(cookie.value) 635 self.assertEqual(cookie.value, '') 636 637 # there should be a distinction between a present but empty value 638 # (above) and a value that's entirely missing (below) 639 640 cookie = c._cookies['www.acme.com']['/']['holyhandgrenade'] 641 self.assertIsNone(cookie.value) 642 643 def test_ns_parser_special_names(self): 644 # names such as 'expires' are not special in first name=value pair 645 # of Set-Cookie: header 646 c = CookieJar() 647 interact_netscape(c, "http://www.acme.com/", 'expires=eggs') 648 interact_netscape(c, "http://www.acme.com/", 'version=eggs; spam=eggs') 649 650 cookies = c._cookies["www.acme.com"]["/"] 651 self.assertIn('expires', cookies) 652 self.assertIn('version', cookies) 653 654 def test_expires(self): 655 # if expires is in future, keep cookie... 656 c = CookieJar() 657 future = time2netscape(time.time()+3600) 658 659 with warnings_helper.check_no_warnings(self): 660 headers = [f"Set-Cookie: FOO=BAR; path=/; expires={future}"] 661 req = urllib.request.Request("http://www.coyote.com/") 662 res = FakeResponse(headers, "http://www.coyote.com/") 663 cookies = c.make_cookies(res, req) 664 self.assertEqual(len(cookies), 1) 665 self.assertEqual(time2netscape(cookies[0].expires), future) 666 667 interact_netscape(c, "http://www.acme.com/", 'spam="bar"; expires=%s' % 668 future) 669 self.assertEqual(len(c), 1) 670 now = time2netscape(time.time()-1) 671 # ... and if in past or present, discard it 672 interact_netscape(c, "http://www.acme.com/", 'foo="eggs"; expires=%s' % 673 now) 674 h = interact_netscape(c, "http://www.acme.com/") 675 self.assertEqual(len(c), 1) 676 self.assertIn('spam="bar"', h) 677 self.assertNotIn("foo", h) 678 679 # max-age takes precedence over expires, and zero max-age is request to 680 # delete both new cookie and any old matching cookie 681 interact_netscape(c, "http://www.acme.com/", 'eggs="bar"; expires=%s' % 682 future) 683 interact_netscape(c, "http://www.acme.com/", 'bar="bar"; expires=%s' % 684 future) 685 self.assertEqual(len(c), 3) 686 interact_netscape(c, "http://www.acme.com/", 'eggs="bar"; ' 687 'expires=%s; max-age=0' % future) 688 interact_netscape(c, "http://www.acme.com/", 'bar="bar"; ' 689 'max-age=0; expires=%s' % future) 690 h = interact_netscape(c, "http://www.acme.com/") 691 self.assertEqual(len(c), 1) 692 693 # test expiry at end of session for cookies with no expires attribute 694 interact_netscape(c, "http://www.rhubarb.net/", 'whum="fizz"') 695 self.assertEqual(len(c), 2) 696 c.clear_session_cookies() 697 self.assertEqual(len(c), 1) 698 self.assertIn('spam="bar"', h) 699 700 # test if fractional expiry is accepted 701 cookie = Cookie(0, "name", "value", 702 None, False, "www.python.org", 703 True, False, "/", 704 False, False, "1444312383.018307", 705 False, None, None, 706 {}) 707 self.assertEqual(cookie.expires, 1444312383) 708 709 # XXX RFC 2965 expiry rules (some apply to V0 too) 710 711 def test_default_path(self): 712 # RFC 2965 713 pol = DefaultCookiePolicy(rfc2965=True) 714 715 c = CookieJar(pol) 716 interact_2965(c, "http://www.acme.com/", 'spam="bar"; Version="1"') 717 self.assertIn("/", c._cookies["www.acme.com"]) 718 719 c = CookieJar(pol) 720 interact_2965(c, "http://www.acme.com/blah", 'eggs="bar"; Version="1"') 721 self.assertIn("/", c._cookies["www.acme.com"]) 722 723 c = CookieJar(pol) 724 interact_2965(c, "http://www.acme.com/blah/rhubarb", 725 'eggs="bar"; Version="1"') 726 self.assertIn("/blah/", c._cookies["www.acme.com"]) 727 728 c = CookieJar(pol) 729 interact_2965(c, "http://www.acme.com/blah/rhubarb/", 730 'eggs="bar"; Version="1"') 731 self.assertIn("/blah/rhubarb/", c._cookies["www.acme.com"]) 732 733 # Netscape 734 735 c = CookieJar() 736 interact_netscape(c, "http://www.acme.com/", 'spam="bar"') 737 self.assertIn("/", c._cookies["www.acme.com"]) 738 739 c = CookieJar() 740 interact_netscape(c, "http://www.acme.com/blah", 'eggs="bar"') 741 self.assertIn("/", c._cookies["www.acme.com"]) 742 743 c = CookieJar() 744 interact_netscape(c, "http://www.acme.com/blah/rhubarb", 'eggs="bar"') 745 self.assertIn("/blah", c._cookies["www.acme.com"]) 746 747 c = CookieJar() 748 interact_netscape(c, "http://www.acme.com/blah/rhubarb/", 'eggs="bar"') 749 self.assertIn("/blah/rhubarb", c._cookies["www.acme.com"]) 750 751 def test_default_path_with_query(self): 752 cj = CookieJar() 753 uri = "http://example.com/?spam/eggs" 754 value = 'eggs="bar"' 755 interact_netscape(cj, uri, value) 756 # Default path does not include query, so is "/", not "/?spam". 757 self.assertIn("/", cj._cookies["example.com"]) 758 # Cookie is sent back to the same URI. 759 self.assertEqual(interact_netscape(cj, uri), value) 760 761 def test_escape_path(self): 762 cases = [ 763 # quoted safe 764 ("/foo%2f/bar", "/foo%2F/bar"), 765 ("/foo%2F/bar", "/foo%2F/bar"), 766 # quoted % 767 ("/foo%%/bar", "/foo%%/bar"), 768 # quoted unsafe 769 ("/fo%19o/bar", "/fo%19o/bar"), 770 ("/fo%7do/bar", "/fo%7Do/bar"), 771 # unquoted safe 772 ("/foo/bar&", "/foo/bar&"), 773 ("/foo//bar", "/foo//bar"), 774 ("\176/foo/bar", "\176/foo/bar"), 775 # unquoted unsafe 776 ("/foo\031/bar", "/foo%19/bar"), 777 ("/\175foo/bar", "/%7Dfoo/bar"), 778 # unicode, latin-1 range 779 ("/foo/bar\u00fc", "/foo/bar%C3%BC"), # UTF-8 encoded 780 # unicode 781 ("/foo/bar\uabcd", "/foo/bar%EA%AF%8D"), # UTF-8 encoded 782 ] 783 for arg, result in cases: 784 self.assertEqual(escape_path(arg), result) 785 786 def test_request_path(self): 787 # with parameters 788 req = urllib.request.Request( 789 "http://www.example.com/rheum/rhaponticum;" 790 "foo=bar;sing=song?apples=pears&spam=eggs#ni") 791 self.assertEqual(request_path(req), 792 "/rheum/rhaponticum;foo=bar;sing=song") 793 # without parameters 794 req = urllib.request.Request( 795 "http://www.example.com/rheum/rhaponticum?" 796 "apples=pears&spam=eggs#ni") 797 self.assertEqual(request_path(req), "/rheum/rhaponticum") 798 # missing final slash 799 req = urllib.request.Request("http://www.example.com") 800 self.assertEqual(request_path(req), "/") 801 802 def test_path_prefix_match(self): 803 pol = DefaultCookiePolicy() 804 strict_ns_path_pol = DefaultCookiePolicy(strict_ns_set_path=True) 805 806 c = CookieJar(pol) 807 base_url = "http://bar.com" 808 interact_netscape(c, base_url, 'spam=eggs; Path=/foo') 809 cookie = c._cookies['bar.com']['/foo']['spam'] 810 811 for path, ok in [('/foo', True), 812 ('/foo/', True), 813 ('/foo/bar', True), 814 ('/', False), 815 ('/foobad/foo', False)]: 816 url = f'{base_url}{path}' 817 req = urllib.request.Request(url) 818 h = interact_netscape(c, url) 819 if ok: 820 self.assertIn('spam=eggs', h, f"cookie not set for {path}") 821 self.assertTrue(strict_ns_path_pol.set_ok_path(cookie, req)) 822 else: 823 self.assertNotIn('spam=eggs', h, f"cookie set for {path}") 824 self.assertFalse(strict_ns_path_pol.set_ok_path(cookie, req)) 825 826 def test_request_port(self): 827 req = urllib.request.Request("http://www.acme.com:1234/", 828 headers={"Host": "www.acme.com:4321"}) 829 self.assertEqual(request_port(req), "1234") 830 req = urllib.request.Request("http://www.acme.com/", 831 headers={"Host": "www.acme.com:4321"}) 832 self.assertEqual(request_port(req), DEFAULT_HTTP_PORT) 833 834 def test_request_host(self): 835 # this request is illegal (RFC2616, 14.2.3) 836 req = urllib.request.Request("http://1.1.1.1/", 837 headers={"Host": "www.acme.com:80"}) 838 # libwww-perl wants this response, but that seems wrong (RFC 2616, 839 # section 5.2, point 1., and RFC 2965 section 1, paragraph 3) 840 #self.assertEqual(request_host(req), "www.acme.com") 841 self.assertEqual(request_host(req), "1.1.1.1") 842 req = urllib.request.Request("http://www.acme.com/", 843 headers={"Host": "irrelevant.com"}) 844 self.assertEqual(request_host(req), "www.acme.com") 845 # port shouldn't be in request-host 846 req = urllib.request.Request("http://www.acme.com:2345/resource.html", 847 headers={"Host": "www.acme.com:5432"}) 848 self.assertEqual(request_host(req), "www.acme.com") 849 850 def test_is_HDN(self): 851 self.assertTrue(is_HDN("foo.bar.com")) 852 self.assertTrue(is_HDN("1foo2.3bar4.5com")) 853 self.assertFalse(is_HDN("192.168.1.1")) 854 self.assertFalse(is_HDN("")) 855 self.assertFalse(is_HDN(".")) 856 self.assertFalse(is_HDN(".foo.bar.com")) 857 self.assertFalse(is_HDN("..foo")) 858 self.assertFalse(is_HDN("foo.")) 859 860 def test_reach(self): 861 self.assertEqual(reach("www.acme.com"), ".acme.com") 862 self.assertEqual(reach("acme.com"), "acme.com") 863 self.assertEqual(reach("acme.local"), ".local") 864 self.assertEqual(reach(".local"), ".local") 865 self.assertEqual(reach(".com"), ".com") 866 self.assertEqual(reach("."), ".") 867 self.assertEqual(reach(""), "") 868 self.assertEqual(reach("192.168.0.1"), "192.168.0.1") 869 870 def test_domain_match(self): 871 self.assertTrue(domain_match("192.168.1.1", "192.168.1.1")) 872 self.assertFalse(domain_match("192.168.1.1", ".168.1.1")) 873 self.assertTrue(domain_match("x.y.com", "x.Y.com")) 874 self.assertTrue(domain_match("x.y.com", ".Y.com")) 875 self.assertFalse(domain_match("x.y.com", "Y.com")) 876 self.assertTrue(domain_match("a.b.c.com", ".c.com")) 877 self.assertFalse(domain_match(".c.com", "a.b.c.com")) 878 self.assertTrue(domain_match("example.local", ".local")) 879 self.assertFalse(domain_match("blah.blah", "")) 880 self.assertFalse(domain_match("", ".rhubarb.rhubarb")) 881 self.assertTrue(domain_match("", "")) 882 883 self.assertTrue(user_domain_match("acme.com", "acme.com")) 884 self.assertFalse(user_domain_match("acme.com", ".acme.com")) 885 self.assertTrue(user_domain_match("rhubarb.acme.com", ".acme.com")) 886 self.assertTrue(user_domain_match("www.rhubarb.acme.com", ".acme.com")) 887 self.assertTrue(user_domain_match("x.y.com", "x.Y.com")) 888 self.assertTrue(user_domain_match("x.y.com", ".Y.com")) 889 self.assertFalse(user_domain_match("x.y.com", "Y.com")) 890 self.assertTrue(user_domain_match("y.com", "Y.com")) 891 self.assertFalse(user_domain_match(".y.com", "Y.com")) 892 self.assertTrue(user_domain_match(".y.com", ".Y.com")) 893 self.assertTrue(user_domain_match("x.y.com", ".com")) 894 self.assertFalse(user_domain_match("x.y.com", "com")) 895 self.assertFalse(user_domain_match("x.y.com", "m")) 896 self.assertFalse(user_domain_match("x.y.com", ".m")) 897 self.assertFalse(user_domain_match("x.y.com", "")) 898 self.assertFalse(user_domain_match("x.y.com", ".")) 899 self.assertTrue(user_domain_match("192.168.1.1", "192.168.1.1")) 900 # not both HDNs, so must string-compare equal to match 901 self.assertFalse(user_domain_match("192.168.1.1", ".168.1.1")) 902 self.assertFalse(user_domain_match("192.168.1.1", ".")) 903 # empty string is a special case 904 self.assertFalse(user_domain_match("192.168.1.1", "")) 905 906 def test_wrong_domain(self): 907 # Cookies whose effective request-host name does not domain-match the 908 # domain are rejected. 909 910 # XXX far from complete 911 c = CookieJar() 912 interact_2965(c, "http://www.nasty.com/", 913 'foo=bar; domain=friendly.org; Version="1"') 914 self.assertEqual(len(c), 0) 915 916 def test_strict_domain(self): 917 # Cookies whose domain is a country-code tld like .co.uk should 918 # not be set if CookiePolicy.strict_domain is true. 919 cp = DefaultCookiePolicy(strict_domain=True) 920 cj = CookieJar(policy=cp) 921 interact_netscape(cj, "http://example.co.uk/", 'no=problemo') 922 interact_netscape(cj, "http://example.co.uk/", 923 'okey=dokey; Domain=.example.co.uk') 924 self.assertEqual(len(cj), 2) 925 for pseudo_tld in [".co.uk", ".org.za", ".tx.us", ".name.us"]: 926 interact_netscape(cj, "http://example.%s/" % pseudo_tld, 927 'spam=eggs; Domain=.co.uk') 928 self.assertEqual(len(cj), 2) 929 930 def test_two_component_domain_ns(self): 931 # Netscape: .www.bar.com, www.bar.com, .bar.com, bar.com, no domain 932 # should all get accepted, as should .acme.com, acme.com and no domain 933 # for 2-component domains like acme.com. 934 c = CookieJar() 935 936 # two-component V0 domain is OK 937 interact_netscape(c, "http://foo.net/", 'ns=bar') 938 self.assertEqual(len(c), 1) 939 self.assertEqual(c._cookies["foo.net"]["/"]["ns"].value, "bar") 940 self.assertEqual(interact_netscape(c, "http://foo.net/"), "ns=bar") 941 # *will* be returned to any other domain (unlike RFC 2965)... 942 self.assertEqual(interact_netscape(c, "http://www.foo.net/"), 943 "ns=bar") 944 # ...unless requested otherwise 945 pol = DefaultCookiePolicy( 946 strict_ns_domain=DefaultCookiePolicy.DomainStrictNonDomain) 947 c.set_policy(pol) 948 self.assertEqual(interact_netscape(c, "http://www.foo.net/"), "") 949 950 # unlike RFC 2965, even explicit two-component domain is OK, 951 # because .foo.net matches foo.net 952 interact_netscape(c, "http://foo.net/foo/", 953 'spam1=eggs; domain=foo.net') 954 # even if starts with a dot -- in NS rules, .foo.net matches foo.net! 955 interact_netscape(c, "http://foo.net/foo/bar/", 956 'spam2=eggs; domain=.foo.net') 957 self.assertEqual(len(c), 3) 958 self.assertEqual(c._cookies[".foo.net"]["/foo"]["spam1"].value, 959 "eggs") 960 self.assertEqual(c._cookies[".foo.net"]["/foo/bar"]["spam2"].value, 961 "eggs") 962 self.assertEqual(interact_netscape(c, "http://foo.net/foo/bar/"), 963 "spam2=eggs; spam1=eggs; ns=bar") 964 965 # top-level domain is too general 966 interact_netscape(c, "http://foo.net/", 'nini="ni"; domain=.net') 967 self.assertEqual(len(c), 3) 968 969## # Netscape protocol doesn't allow non-special top level domains (such 970## # as co.uk) in the domain attribute unless there are at least three 971## # dots in it. 972 # Oh yes it does! Real implementations don't check this, and real 973 # cookies (of course) rely on that behaviour. 974 interact_netscape(c, "http://foo.co.uk", 'nasty=trick; domain=.co.uk') 975## self.assertEqual(len(c), 2) 976 self.assertEqual(len(c), 4) 977 978 def test_localhost_domain(self): 979 c = CookieJar() 980 981 interact_netscape(c, "http://localhost", "foo=bar; domain=localhost;") 982 983 self.assertEqual(len(c), 1) 984 985 def test_localhost_domain_contents(self): 986 c = CookieJar() 987 988 interact_netscape(c, "http://localhost", "foo=bar; domain=localhost;") 989 990 self.assertEqual(c._cookies[".localhost"]["/"]["foo"].value, "bar") 991 992 def test_localhost_domain_contents_2(self): 993 c = CookieJar() 994 995 interact_netscape(c, "http://localhost", "foo=bar;") 996 997 self.assertEqual(c._cookies["localhost.local"]["/"]["foo"].value, "bar") 998 999 def test_evil_nonlocal_domain(self): 1000 c = CookieJar() 1001 1002 interact_netscape(c, "http://evil.com", "foo=bar; domain=.localhost") 1003 1004 self.assertEqual(len(c), 0) 1005 1006 def test_evil_local_domain(self): 1007 c = CookieJar() 1008 1009 interact_netscape(c, "http://localhost", "foo=bar; domain=.evil.com") 1010 1011 self.assertEqual(len(c), 0) 1012 1013 def test_evil_local_domain_2(self): 1014 c = CookieJar() 1015 1016 interact_netscape(c, "http://localhost", "foo=bar; domain=.someother.local") 1017 1018 self.assertEqual(len(c), 0) 1019 1020 def test_two_component_domain_rfc2965(self): 1021 pol = DefaultCookiePolicy(rfc2965=True) 1022 c = CookieJar(pol) 1023 1024 # two-component V1 domain is OK 1025 interact_2965(c, "http://foo.net/", 'foo=bar; Version="1"') 1026 self.assertEqual(len(c), 1) 1027 self.assertEqual(c._cookies["foo.net"]["/"]["foo"].value, "bar") 1028 self.assertEqual(interact_2965(c, "http://foo.net/"), 1029 "$Version=1; foo=bar") 1030 # won't be returned to any other domain (because domain was implied) 1031 self.assertEqual(interact_2965(c, "http://www.foo.net/"), "") 1032 1033 # unless domain is given explicitly, because then it must be 1034 # rewritten to start with a dot: foo.net --> .foo.net, which does 1035 # not domain-match foo.net 1036 interact_2965(c, "http://foo.net/foo", 1037 'spam=eggs; domain=foo.net; path=/foo; Version="1"') 1038 self.assertEqual(len(c), 1) 1039 self.assertEqual(interact_2965(c, "http://foo.net/foo"), 1040 "$Version=1; foo=bar") 1041 1042 # explicit foo.net from three-component domain www.foo.net *does* get 1043 # set, because .foo.net domain-matches .foo.net 1044 interact_2965(c, "http://www.foo.net/foo/", 1045 'spam=eggs; domain=foo.net; Version="1"') 1046 self.assertEqual(c._cookies[".foo.net"]["/foo/"]["spam"].value, 1047 "eggs") 1048 self.assertEqual(len(c), 2) 1049 self.assertEqual(interact_2965(c, "http://foo.net/foo/"), 1050 "$Version=1; foo=bar") 1051 self.assertEqual(interact_2965(c, "http://www.foo.net/foo/"), 1052 '$Version=1; spam=eggs; $Domain="foo.net"') 1053 1054 # top-level domain is too general 1055 interact_2965(c, "http://foo.net/", 1056 'ni="ni"; domain=".net"; Version="1"') 1057 self.assertEqual(len(c), 2) 1058 1059 # RFC 2965 doesn't require blocking this 1060 interact_2965(c, "http://foo.co.uk/", 1061 'nasty=trick; domain=.co.uk; Version="1"') 1062 self.assertEqual(len(c), 3) 1063 1064 def test_domain_allow(self): 1065 c = CookieJar(policy=DefaultCookiePolicy( 1066 blocked_domains=["acme.com"], 1067 allowed_domains=["www.acme.com"])) 1068 1069 req = urllib.request.Request("http://acme.com/") 1070 headers = ["Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/"] 1071 res = FakeResponse(headers, "http://acme.com/") 1072 c.extract_cookies(res, req) 1073 self.assertEqual(len(c), 0) 1074 1075 req = urllib.request.Request("http://www.acme.com/") 1076 res = FakeResponse(headers, "http://www.acme.com/") 1077 c.extract_cookies(res, req) 1078 self.assertEqual(len(c), 1) 1079 1080 req = urllib.request.Request("http://www.coyote.com/") 1081 res = FakeResponse(headers, "http://www.coyote.com/") 1082 c.extract_cookies(res, req) 1083 self.assertEqual(len(c), 1) 1084 1085 # set a cookie with non-allowed domain... 1086 req = urllib.request.Request("http://www.coyote.com/") 1087 res = FakeResponse(headers, "http://www.coyote.com/") 1088 cookies = c.make_cookies(res, req) 1089 c.set_cookie(cookies[0]) 1090 self.assertEqual(len(c), 2) 1091 # ... and check is doesn't get returned 1092 c.add_cookie_header(req) 1093 self.assertFalse(req.has_header("Cookie")) 1094 1095 def test_domain_block(self): 1096 pol = DefaultCookiePolicy( 1097 rfc2965=True, blocked_domains=[".acme.com"]) 1098 c = CookieJar(policy=pol) 1099 headers = ["Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/"] 1100 1101 req = urllib.request.Request("http://www.acme.com/") 1102 res = FakeResponse(headers, "http://www.acme.com/") 1103 c.extract_cookies(res, req) 1104 self.assertEqual(len(c), 0) 1105 1106 p = pol.set_blocked_domains(["acme.com"]) 1107 c.extract_cookies(res, req) 1108 self.assertEqual(len(c), 1) 1109 1110 c.clear() 1111 req = urllib.request.Request("http://www.roadrunner.net/") 1112 res = FakeResponse(headers, "http://www.roadrunner.net/") 1113 c.extract_cookies(res, req) 1114 self.assertEqual(len(c), 1) 1115 req = urllib.request.Request("http://www.roadrunner.net/") 1116 c.add_cookie_header(req) 1117 self.assertTrue(req.has_header("Cookie")) 1118 self.assertTrue(req.has_header("Cookie2")) 1119 1120 c.clear() 1121 pol.set_blocked_domains([".acme.com"]) 1122 c.extract_cookies(res, req) 1123 self.assertEqual(len(c), 1) 1124 1125 # set a cookie with blocked domain... 1126 req = urllib.request.Request("http://www.acme.com/") 1127 res = FakeResponse(headers, "http://www.acme.com/") 1128 cookies = c.make_cookies(res, req) 1129 c.set_cookie(cookies[0]) 1130 self.assertEqual(len(c), 2) 1131 # ... and check is doesn't get returned 1132 c.add_cookie_header(req) 1133 self.assertFalse(req.has_header("Cookie")) 1134 1135 c.clear() 1136 1137 pol.set_blocked_domains([]) 1138 req = urllib.request.Request("http://acme.com/") 1139 res = FakeResponse(headers, "http://acme.com/") 1140 cookies = c.make_cookies(res, req) 1141 c.extract_cookies(res, req) 1142 self.assertEqual(len(c), 1) 1143 1144 req = urllib.request.Request("http://acme.com/") 1145 c.add_cookie_header(req) 1146 self.assertTrue(req.has_header("Cookie")) 1147 1148 req = urllib.request.Request("http://badacme.com/") 1149 c.add_cookie_header(req) 1150 self.assertFalse(pol.return_ok(cookies[0], req)) 1151 self.assertFalse(req.has_header("Cookie")) 1152 1153 p = pol.set_blocked_domains(["acme.com"]) 1154 req = urllib.request.Request("http://acme.com/") 1155 c.add_cookie_header(req) 1156 self.assertFalse(req.has_header("Cookie")) 1157 1158 req = urllib.request.Request("http://badacme.com/") 1159 c.add_cookie_header(req) 1160 self.assertFalse(req.has_header("Cookie")) 1161 1162 def test_secure(self): 1163 for ns in True, False: 1164 for whitespace in " ", "": 1165 c = CookieJar() 1166 if ns: 1167 pol = DefaultCookiePolicy(rfc2965=False) 1168 int = interact_netscape 1169 vs = "" 1170 else: 1171 pol = DefaultCookiePolicy(rfc2965=True) 1172 int = interact_2965 1173 vs = "; Version=1" 1174 c.set_policy(pol) 1175 url = "http://www.acme.com/" 1176 int(c, url, "foo1=bar%s%s" % (vs, whitespace)) 1177 int(c, url, "foo2=bar%s; secure%s" % (vs, whitespace)) 1178 self.assertFalse( 1179 c._cookies["www.acme.com"]["/"]["foo1"].secure, 1180 "non-secure cookie registered secure") 1181 self.assertTrue( 1182 c._cookies["www.acme.com"]["/"]["foo2"].secure, 1183 "secure cookie registered non-secure") 1184 1185 def test_secure_block(self): 1186 pol = DefaultCookiePolicy() 1187 c = CookieJar(policy=pol) 1188 1189 headers = ["Set-Cookie: session=narf; secure; path=/"] 1190 req = urllib.request.Request("https://www.acme.com/") 1191 res = FakeResponse(headers, "https://www.acme.com/") 1192 c.extract_cookies(res, req) 1193 self.assertEqual(len(c), 1) 1194 1195 req = urllib.request.Request("https://www.acme.com/") 1196 c.add_cookie_header(req) 1197 self.assertTrue(req.has_header("Cookie")) 1198 1199 req = urllib.request.Request("http://www.acme.com/") 1200 c.add_cookie_header(req) 1201 self.assertFalse(req.has_header("Cookie")) 1202 1203 # secure websocket protocol 1204 req = urllib.request.Request("wss://www.acme.com/") 1205 c.add_cookie_header(req) 1206 self.assertTrue(req.has_header("Cookie")) 1207 1208 # non-secure websocket protocol 1209 req = urllib.request.Request("ws://www.acme.com/") 1210 c.add_cookie_header(req) 1211 self.assertFalse(req.has_header("Cookie")) 1212 1213 def test_custom_secure_protocols(self): 1214 pol = DefaultCookiePolicy(secure_protocols=["foos"]) 1215 c = CookieJar(policy=pol) 1216 1217 headers = ["Set-Cookie: session=narf; secure; path=/"] 1218 req = urllib.request.Request("https://www.acme.com/") 1219 res = FakeResponse(headers, "https://www.acme.com/") 1220 c.extract_cookies(res, req) 1221 self.assertEqual(len(c), 1) 1222 1223 # test https removed from secure protocol list 1224 req = urllib.request.Request("https://www.acme.com/") 1225 c.add_cookie_header(req) 1226 self.assertFalse(req.has_header("Cookie")) 1227 1228 req = urllib.request.Request("http://www.acme.com/") 1229 c.add_cookie_header(req) 1230 self.assertFalse(req.has_header("Cookie")) 1231 1232 req = urllib.request.Request("foos://www.acme.com/") 1233 c.add_cookie_header(req) 1234 self.assertTrue(req.has_header("Cookie")) 1235 1236 req = urllib.request.Request("foo://www.acme.com/") 1237 c.add_cookie_header(req) 1238 self.assertFalse(req.has_header("Cookie")) 1239 1240 def test_quote_cookie_value(self): 1241 c = CookieJar(policy=DefaultCookiePolicy(rfc2965=True)) 1242 interact_2965(c, "http://www.acme.com/", r'foo=\b"a"r; Version=1') 1243 h = interact_2965(c, "http://www.acme.com/") 1244 self.assertEqual(h, r'$Version=1; foo=\\b\"a\"r') 1245 1246 def test_missing_final_slash(self): 1247 # Missing slash from request URL's abs_path should be assumed present. 1248 url = "http://www.acme.com" 1249 c = CookieJar(DefaultCookiePolicy(rfc2965=True)) 1250 interact_2965(c, url, "foo=bar; Version=1") 1251 req = urllib.request.Request(url) 1252 self.assertEqual(len(c), 1) 1253 c.add_cookie_header(req) 1254 self.assertTrue(req.has_header("Cookie")) 1255 1256 def test_domain_mirror(self): 1257 pol = DefaultCookiePolicy(rfc2965=True) 1258 1259 c = CookieJar(pol) 1260 url = "http://foo.bar.com/" 1261 interact_2965(c, url, "spam=eggs; Version=1") 1262 h = interact_2965(c, url) 1263 self.assertNotIn("Domain", h, 1264 "absent domain returned with domain present") 1265 1266 c = CookieJar(pol) 1267 url = "http://foo.bar.com/" 1268 interact_2965(c, url, 'spam=eggs; Version=1; Domain=.bar.com') 1269 h = interact_2965(c, url) 1270 self.assertIn('$Domain=".bar.com"', h, "domain not returned") 1271 1272 c = CookieJar(pol) 1273 url = "http://foo.bar.com/" 1274 # note missing initial dot in Domain 1275 interact_2965(c, url, 'spam=eggs; Version=1; Domain=bar.com') 1276 h = interact_2965(c, url) 1277 self.assertIn('$Domain="bar.com"', h, "domain not returned") 1278 1279 def test_path_mirror(self): 1280 pol = DefaultCookiePolicy(rfc2965=True) 1281 1282 c = CookieJar(pol) 1283 url = "http://foo.bar.com/" 1284 interact_2965(c, url, "spam=eggs; Version=1") 1285 h = interact_2965(c, url) 1286 self.assertNotIn("Path", h, "absent path returned with path present") 1287 1288 c = CookieJar(pol) 1289 url = "http://foo.bar.com/" 1290 interact_2965(c, url, 'spam=eggs; Version=1; Path=/') 1291 h = interact_2965(c, url) 1292 self.assertIn('$Path="/"', h, "path not returned") 1293 1294 def test_port_mirror(self): 1295 pol = DefaultCookiePolicy(rfc2965=True) 1296 1297 c = CookieJar(pol) 1298 url = "http://foo.bar.com/" 1299 interact_2965(c, url, "spam=eggs; Version=1") 1300 h = interact_2965(c, url) 1301 self.assertNotIn("Port", h, "absent port returned with port present") 1302 1303 c = CookieJar(pol) 1304 url = "http://foo.bar.com/" 1305 interact_2965(c, url, "spam=eggs; Version=1; Port") 1306 h = interact_2965(c, url) 1307 self.assertRegex(h, r"\$Port([^=]|$)", 1308 "port with no value not returned with no value") 1309 1310 c = CookieJar(pol) 1311 url = "http://foo.bar.com/" 1312 interact_2965(c, url, 'spam=eggs; Version=1; Port="80"') 1313 h = interact_2965(c, url) 1314 self.assertIn('$Port="80"', h, 1315 "port with single value not returned with single value") 1316 1317 c = CookieJar(pol) 1318 url = "http://foo.bar.com/" 1319 interact_2965(c, url, 'spam=eggs; Version=1; Port="80,8080"') 1320 h = interact_2965(c, url) 1321 self.assertIn('$Port="80,8080"', h, 1322 "port with multiple values not returned with multiple " 1323 "values") 1324 1325 def test_no_return_comment(self): 1326 c = CookieJar(DefaultCookiePolicy(rfc2965=True)) 1327 url = "http://foo.bar.com/" 1328 interact_2965(c, url, 'spam=eggs; Version=1; ' 1329 'Comment="does anybody read these?"; ' 1330 'CommentURL="http://foo.bar.net/comment.html"') 1331 h = interact_2965(c, url) 1332 self.assertNotIn("Comment", h, 1333 "Comment or CommentURL cookie-attributes returned to server") 1334 1335 def test_Cookie_iterator(self): 1336 cs = CookieJar(DefaultCookiePolicy(rfc2965=True)) 1337 # add some random cookies 1338 interact_2965(cs, "http://blah.spam.org/", 'foo=eggs; Version=1; ' 1339 'Comment="does anybody read these?"; ' 1340 'CommentURL="http://foo.bar.net/comment.html"') 1341 interact_netscape(cs, "http://www.acme.com/blah/", "spam=bar; secure") 1342 interact_2965(cs, "http://www.acme.com/blah/", 1343 "foo=bar; secure; Version=1") 1344 interact_2965(cs, "http://www.acme.com/blah/", 1345 "foo=bar; path=/; Version=1") 1346 interact_2965(cs, "http://www.sol.no", 1347 r'bang=wallop; version=1; domain=".sol.no"; ' 1348 r'port="90,100, 80,8080"; ' 1349 r'max-age=100; Comment = "Just kidding! (\"|\\\\) "') 1350 1351 versions = [1, 0, 1, 1, 1] 1352 names = ["foo", "spam", "foo", "foo", "bang"] 1353 domains = ["blah.spam.org", "www.acme.com", "www.acme.com", 1354 "www.acme.com", ".sol.no"] 1355 paths = ["/", "/blah", "/blah/", "/", "/"] 1356 1357 for i in range(4): 1358 i = 0 1359 for c in cs: 1360 self.assertIsInstance(c, Cookie) 1361 self.assertEqual(c.version, versions[i]) 1362 self.assertEqual(c.name, names[i]) 1363 self.assertEqual(c.domain, domains[i]) 1364 self.assertEqual(c.path, paths[i]) 1365 i = i + 1 1366 1367 def test_parse_ns_headers(self): 1368 # missing domain value (invalid cookie) 1369 self.assertEqual( 1370 parse_ns_headers(["foo=bar; path=/; domain"]), 1371 [[("foo", "bar"), 1372 ("path", "/"), ("domain", None), ("version", "0")]] 1373 ) 1374 # invalid expires value 1375 self.assertEqual( 1376 parse_ns_headers(["foo=bar; expires=Foo Bar 12 33:22:11 2000"]), 1377 [[("foo", "bar"), ("expires", None), ("version", "0")]] 1378 ) 1379 # missing cookie value (valid cookie) 1380 self.assertEqual( 1381 parse_ns_headers(["foo"]), 1382 [[("foo", None), ("version", "0")]] 1383 ) 1384 # missing cookie values for parsed attributes 1385 self.assertEqual( 1386 parse_ns_headers(['foo=bar; expires']), 1387 [[('foo', 'bar'), ('expires', None), ('version', '0')]]) 1388 self.assertEqual( 1389 parse_ns_headers(['foo=bar; version']), 1390 [[('foo', 'bar'), ('version', None)]]) 1391 # shouldn't add version if header is empty 1392 self.assertEqual(parse_ns_headers([""]), []) 1393 1394 def test_bad_cookie_header(self): 1395 1396 def cookiejar_from_cookie_headers(headers): 1397 c = CookieJar() 1398 req = urllib.request.Request("http://www.example.com/") 1399 r = FakeResponse(headers, "http://www.example.com/") 1400 c.extract_cookies(r, req) 1401 return c 1402 1403 future = time2netscape(time.time()+3600) 1404 1405 # none of these bad headers should cause an exception to be raised 1406 for headers in [ 1407 ["Set-Cookie: "], # actually, nothing wrong with this 1408 ["Set-Cookie2: "], # ditto 1409 # missing domain value 1410 ["Set-Cookie2: a=foo; path=/; Version=1; domain"], 1411 # bad max-age 1412 ["Set-Cookie: b=foo; max-age=oops"], 1413 # bad version 1414 ["Set-Cookie: b=foo; version=spam"], 1415 ["Set-Cookie:; Expires=%s" % future], 1416 ]: 1417 c = cookiejar_from_cookie_headers(headers) 1418 # these bad cookies shouldn't be set 1419 self.assertEqual(len(c), 0) 1420 1421 # cookie with invalid expires is treated as session cookie 1422 headers = ["Set-Cookie: c=foo; expires=Foo Bar 12 33:22:11 2000"] 1423 c = cookiejar_from_cookie_headers(headers) 1424 cookie = c._cookies["www.example.com"]["/"]["c"] 1425 self.assertIsNone(cookie.expires) 1426 1427 1428class LWPCookieTests(unittest.TestCase): 1429 # Tests taken from libwww-perl, with a few modifications and additions. 1430 1431 def test_netscape_example_1(self): 1432 #------------------------------------------------------------------- 1433 # First we check that it works for the original example at 1434 # http://www.netscape.com/newsref/std/cookie_spec.html 1435 1436 # Client requests a document, and receives in the response: 1437 # 1438 # Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT 1439 # 1440 # When client requests a URL in path "/" on this server, it sends: 1441 # 1442 # Cookie: CUSTOMER=WILE_E_COYOTE 1443 # 1444 # Client requests a document, and receives in the response: 1445 # 1446 # Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/ 1447 # 1448 # When client requests a URL in path "/" on this server, it sends: 1449 # 1450 # Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001 1451 # 1452 # Client receives: 1453 # 1454 # Set-Cookie: SHIPPING=FEDEX; path=/fo 1455 # 1456 # When client requests a URL in path "/" on this server, it sends: 1457 # 1458 # Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001 1459 # 1460 # When client requests a URL in path "/foo" on this server, it sends: 1461 # 1462 # Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX 1463 # 1464 # The last Cookie is buggy, because both specifications say that the 1465 # most specific cookie must be sent first. SHIPPING=FEDEX is the 1466 # most specific and should thus be first. 1467 1468 year_plus_one = time.localtime()[0] + 1 1469 1470 headers = [] 1471 1472 c = CookieJar(DefaultCookiePolicy(rfc2965 = True)) 1473 1474 #req = urllib.request.Request("http://1.1.1.1/", 1475 # headers={"Host": "www.acme.com:80"}) 1476 req = urllib.request.Request("http://www.acme.com:80/", 1477 headers={"Host": "www.acme.com:80"}) 1478 1479 headers.append( 1480 "Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/ ; " 1481 "expires=Wednesday, 09-Nov-%d 23:12:40 GMT" % year_plus_one) 1482 res = FakeResponse(headers, "http://www.acme.com/") 1483 c.extract_cookies(res, req) 1484 1485 req = urllib.request.Request("http://www.acme.com/") 1486 c.add_cookie_header(req) 1487 1488 self.assertEqual(req.get_header("Cookie"), "CUSTOMER=WILE_E_COYOTE") 1489 self.assertEqual(req.get_header("Cookie2"), '$Version="1"') 1490 1491 headers.append("Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/") 1492 res = FakeResponse(headers, "http://www.acme.com/") 1493 c.extract_cookies(res, req) 1494 1495 req = urllib.request.Request("http://www.acme.com/foo/bar") 1496 c.add_cookie_header(req) 1497 1498 h = req.get_header("Cookie") 1499 self.assertIn("PART_NUMBER=ROCKET_LAUNCHER_0001", h) 1500 self.assertIn("CUSTOMER=WILE_E_COYOTE", h) 1501 1502 headers.append('Set-Cookie: SHIPPING=FEDEX; path=/foo') 1503 res = FakeResponse(headers, "http://www.acme.com") 1504 c.extract_cookies(res, req) 1505 1506 req = urllib.request.Request("http://www.acme.com/") 1507 c.add_cookie_header(req) 1508 1509 h = req.get_header("Cookie") 1510 self.assertIn("PART_NUMBER=ROCKET_LAUNCHER_0001", h) 1511 self.assertIn("CUSTOMER=WILE_E_COYOTE", h) 1512 self.assertNotIn("SHIPPING=FEDEX", h) 1513 1514 req = urllib.request.Request("http://www.acme.com/foo/") 1515 c.add_cookie_header(req) 1516 1517 h = req.get_header("Cookie") 1518 self.assertIn("PART_NUMBER=ROCKET_LAUNCHER_0001", h) 1519 self.assertIn("CUSTOMER=WILE_E_COYOTE", h) 1520 self.assertTrue(h.startswith("SHIPPING=FEDEX;")) 1521 1522 def test_netscape_example_2(self): 1523 # Second Example transaction sequence: 1524 # 1525 # Assume all mappings from above have been cleared. 1526 # 1527 # Client receives: 1528 # 1529 # Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/ 1530 # 1531 # When client requests a URL in path "/" on this server, it sends: 1532 # 1533 # Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001 1534 # 1535 # Client receives: 1536 # 1537 # Set-Cookie: PART_NUMBER=RIDING_ROCKET_0023; path=/ammo 1538 # 1539 # When client requests a URL in path "/ammo" on this server, it sends: 1540 # 1541 # Cookie: PART_NUMBER=RIDING_ROCKET_0023; PART_NUMBER=ROCKET_LAUNCHER_0001 1542 # 1543 # NOTE: There are two name/value pairs named "PART_NUMBER" due to 1544 # the inheritance of the "/" mapping in addition to the "/ammo" mapping. 1545 1546 c = CookieJar() 1547 headers = [] 1548 1549 req = urllib.request.Request("http://www.acme.com/") 1550 headers.append("Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/") 1551 res = FakeResponse(headers, "http://www.acme.com/") 1552 1553 c.extract_cookies(res, req) 1554 1555 req = urllib.request.Request("http://www.acme.com/") 1556 c.add_cookie_header(req) 1557 1558 self.assertEqual(req.get_header("Cookie"), 1559 "PART_NUMBER=ROCKET_LAUNCHER_0001") 1560 1561 headers.append( 1562 "Set-Cookie: PART_NUMBER=RIDING_ROCKET_0023; path=/ammo") 1563 res = FakeResponse(headers, "http://www.acme.com/") 1564 c.extract_cookies(res, req) 1565 1566 req = urllib.request.Request("http://www.acme.com/ammo") 1567 c.add_cookie_header(req) 1568 1569 self.assertRegex(req.get_header("Cookie"), 1570 r"PART_NUMBER=RIDING_ROCKET_0023;\s*" 1571 "PART_NUMBER=ROCKET_LAUNCHER_0001") 1572 1573 def test_ietf_example_1(self): 1574 #------------------------------------------------------------------- 1575 # Then we test with the examples from draft-ietf-http-state-man-mec-03.txt 1576 # 1577 # 5. EXAMPLES 1578 1579 c = CookieJar(DefaultCookiePolicy(rfc2965=True)) 1580 1581 # 1582 # 5.1 Example 1 1583 # 1584 # Most detail of request and response headers has been omitted. Assume 1585 # the user agent has no stored cookies. 1586 # 1587 # 1. User Agent -> Server 1588 # 1589 # POST /acme/login HTTP/1.1 1590 # [form data] 1591 # 1592 # User identifies self via a form. 1593 # 1594 # 2. Server -> User Agent 1595 # 1596 # HTTP/1.1 200 OK 1597 # Set-Cookie2: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme" 1598 # 1599 # Cookie reflects user's identity. 1600 1601 cookie = interact_2965( 1602 c, 'http://www.acme.com/acme/login', 1603 'Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"') 1604 self.assertFalse(cookie) 1605 1606 # 1607 # 3. User Agent -> Server 1608 # 1609 # POST /acme/pickitem HTTP/1.1 1610 # Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme" 1611 # [form data] 1612 # 1613 # User selects an item for ``shopping basket.'' 1614 # 1615 # 4. Server -> User Agent 1616 # 1617 # HTTP/1.1 200 OK 1618 # Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1"; 1619 # Path="/acme" 1620 # 1621 # Shopping basket contains an item. 1622 1623 cookie = interact_2965(c, 'http://www.acme.com/acme/pickitem', 1624 'Part_Number="Rocket_Launcher_0001"; ' 1625 'Version="1"; Path="/acme"'); 1626 self.assertRegex(cookie, 1627 r'^\$Version="?1"?; Customer="?WILE_E_COYOTE"?; \$Path="/acme"$') 1628 1629 # 1630 # 5. User Agent -> Server 1631 # 1632 # POST /acme/shipping HTTP/1.1 1633 # Cookie: $Version="1"; 1634 # Customer="WILE_E_COYOTE"; $Path="/acme"; 1635 # Part_Number="Rocket_Launcher_0001"; $Path="/acme" 1636 # [form data] 1637 # 1638 # User selects shipping method from form. 1639 # 1640 # 6. Server -> User Agent 1641 # 1642 # HTTP/1.1 200 OK 1643 # Set-Cookie2: Shipping="FedEx"; Version="1"; Path="/acme" 1644 # 1645 # New cookie reflects shipping method. 1646 1647 cookie = interact_2965(c, "http://www.acme.com/acme/shipping", 1648 'Shipping="FedEx"; Version="1"; Path="/acme"') 1649 1650 self.assertRegex(cookie, r'^\$Version="?1"?;') 1651 self.assertRegex(cookie, r'Part_Number="?Rocket_Launcher_0001"?;' 1652 r'\s*\$Path="\/acme"') 1653 self.assertRegex(cookie, r'Customer="?WILE_E_COYOTE"?;' 1654 r'\s*\$Path="\/acme"') 1655 1656 # 1657 # 7. User Agent -> Server 1658 # 1659 # POST /acme/process HTTP/1.1 1660 # Cookie: $Version="1"; 1661 # Customer="WILE_E_COYOTE"; $Path="/acme"; 1662 # Part_Number="Rocket_Launcher_0001"; $Path="/acme"; 1663 # Shipping="FedEx"; $Path="/acme" 1664 # [form data] 1665 # 1666 # User chooses to process order. 1667 # 1668 # 8. Server -> User Agent 1669 # 1670 # HTTP/1.1 200 OK 1671 # 1672 # Transaction is complete. 1673 1674 cookie = interact_2965(c, "http://www.acme.com/acme/process") 1675 self.assertRegex(cookie, r'Shipping="?FedEx"?;\s*\$Path="\/acme"') 1676 self.assertIn("WILE_E_COYOTE", cookie) 1677 1678 # 1679 # The user agent makes a series of requests on the origin server, after 1680 # each of which it receives a new cookie. All the cookies have the same 1681 # Path attribute and (default) domain. Because the request URLs all have 1682 # /acme as a prefix, and that matches the Path attribute, each request 1683 # contains all the cookies received so far. 1684 1685 def test_ietf_example_2(self): 1686 # 5.2 Example 2 1687 # 1688 # This example illustrates the effect of the Path attribute. All detail 1689 # of request and response headers has been omitted. Assume the user agent 1690 # has no stored cookies. 1691 1692 c = CookieJar(DefaultCookiePolicy(rfc2965=True)) 1693 1694 # Imagine the user agent has received, in response to earlier requests, 1695 # the response headers 1696 # 1697 # Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1"; 1698 # Path="/acme" 1699 # 1700 # and 1701 # 1702 # Set-Cookie2: Part_Number="Riding_Rocket_0023"; Version="1"; 1703 # Path="/acme/ammo" 1704 1705 interact_2965( 1706 c, "http://www.acme.com/acme/ammo/specific", 1707 'Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"', 1708 'Part_Number="Riding_Rocket_0023"; Version="1"; Path="/acme/ammo"') 1709 1710 # A subsequent request by the user agent to the (same) server for URLs of 1711 # the form /acme/ammo/... would include the following request header: 1712 # 1713 # Cookie: $Version="1"; 1714 # Part_Number="Riding_Rocket_0023"; $Path="/acme/ammo"; 1715 # Part_Number="Rocket_Launcher_0001"; $Path="/acme" 1716 # 1717 # Note that the NAME=VALUE pair for the cookie with the more specific Path 1718 # attribute, /acme/ammo, comes before the one with the less specific Path 1719 # attribute, /acme. Further note that the same cookie name appears more 1720 # than once. 1721 1722 cookie = interact_2965(c, "http://www.acme.com/acme/ammo/...") 1723 self.assertRegex(cookie, r"Riding_Rocket_0023.*Rocket_Launcher_0001") 1724 1725 # A subsequent request by the user agent to the (same) server for a URL of 1726 # the form /acme/parts/ would include the following request header: 1727 # 1728 # Cookie: $Version="1"; Part_Number="Rocket_Launcher_0001"; $Path="/acme" 1729 # 1730 # Here, the second cookie's Path attribute /acme/ammo is not a prefix of 1731 # the request URL, /acme/parts/, so the cookie does not get forwarded to 1732 # the server. 1733 1734 cookie = interact_2965(c, "http://www.acme.com/acme/parts/") 1735 self.assertIn("Rocket_Launcher_0001", cookie) 1736 self.assertNotIn("Riding_Rocket_0023", cookie) 1737 1738 def test_rejection(self): 1739 # Test rejection of Set-Cookie2 responses based on domain, path, port. 1740 pol = DefaultCookiePolicy(rfc2965=True) 1741 1742 c = LWPCookieJar(policy=pol) 1743 1744 max_age = "max-age=3600" 1745 1746 # illegal domain (no embedded dots) 1747 cookie = interact_2965(c, "http://www.acme.com", 1748 'foo=bar; domain=".com"; version=1') 1749 self.assertFalse(c) 1750 1751 # legal domain 1752 cookie = interact_2965(c, "http://www.acme.com", 1753 'ping=pong; domain="acme.com"; version=1') 1754 self.assertEqual(len(c), 1) 1755 1756 # illegal domain (host prefix "www.a" contains a dot) 1757 cookie = interact_2965(c, "http://www.a.acme.com", 1758 'whiz=bang; domain="acme.com"; version=1') 1759 self.assertEqual(len(c), 1) 1760 1761 # legal domain 1762 cookie = interact_2965(c, "http://www.a.acme.com", 1763 'wow=flutter; domain=".a.acme.com"; version=1') 1764 self.assertEqual(len(c), 2) 1765 1766 # can't partially match an IP-address 1767 cookie = interact_2965(c, "http://125.125.125.125", 1768 'zzzz=ping; domain="125.125.125"; version=1') 1769 self.assertEqual(len(c), 2) 1770 1771 # illegal path (must be prefix of request path) 1772 cookie = interact_2965(c, "http://www.sol.no", 1773 'blah=rhubarb; domain=".sol.no"; path="/foo"; ' 1774 'version=1') 1775 self.assertEqual(len(c), 2) 1776 1777 # legal path 1778 cookie = interact_2965(c, "http://www.sol.no/foo/bar", 1779 'bing=bong; domain=".sol.no"; path="/foo"; ' 1780 'version=1') 1781 self.assertEqual(len(c), 3) 1782 1783 # illegal port (request-port not in list) 1784 cookie = interact_2965(c, "http://www.sol.no", 1785 'whiz=ffft; domain=".sol.no"; port="90,100"; ' 1786 'version=1') 1787 self.assertEqual(len(c), 3) 1788 1789 # legal port 1790 cookie = interact_2965( 1791 c, "http://www.sol.no", 1792 r'bang=wallop; version=1; domain=".sol.no"; ' 1793 r'port="90,100, 80,8080"; ' 1794 r'max-age=100; Comment = "Just kidding! (\"|\\\\) "') 1795 self.assertEqual(len(c), 4) 1796 1797 # port attribute without any value (current port) 1798 cookie = interact_2965(c, "http://www.sol.no", 1799 'foo9=bar; version=1; domain=".sol.no"; port; ' 1800 'max-age=100;') 1801 self.assertEqual(len(c), 5) 1802 1803 # encoded path 1804 # LWP has this test, but unescaping allowed path characters seems 1805 # like a bad idea, so I think this should fail: 1806## cookie = interact_2965(c, "http://www.sol.no/foo/", 1807## r'foo8=bar; version=1; path="/%66oo"') 1808 # but this is OK, because '<' is not an allowed HTTP URL path 1809 # character: 1810 cookie = interact_2965(c, "http://www.sol.no/<oo/", 1811 r'foo8=bar; version=1; path="/%3coo"') 1812 self.assertEqual(len(c), 6) 1813 1814 # save and restore 1815 filename = os_helper.TESTFN 1816 1817 try: 1818 c.save(filename, ignore_discard=True) 1819 old = repr(c) 1820 1821 c = LWPCookieJar(policy=pol) 1822 c.load(filename, ignore_discard=True) 1823 finally: 1824 os_helper.unlink(filename) 1825 1826 self.assertEqual(old, repr(c)) 1827 1828 def test_url_encoding(self): 1829 # Try some URL encodings of the PATHs. 1830 # (the behaviour here has changed from libwww-perl) 1831 c = CookieJar(DefaultCookiePolicy(rfc2965=True)) 1832 interact_2965(c, "http://www.acme.com/foo%2f%25/" 1833 "%3c%3c%0Anew%C3%A5/%C3%A5", 1834 "foo = bar; version = 1") 1835 1836 cookie = interact_2965( 1837 c, "http://www.acme.com/foo%2f%25/<<%0anew\345/\346\370\345", 1838 'bar=baz; path="/foo/"; version=1'); 1839 version_re = re.compile(r'^\$version=\"?1\"?', re.I) 1840 self.assertIn("foo=bar", cookie) 1841 self.assertRegex(cookie, version_re) 1842 1843 cookie = interact_2965( 1844 c, "http://www.acme.com/foo/%25/<<%0anew\345/\346\370\345") 1845 self.assertFalse(cookie) 1846 1847 # unicode URL doesn't raise exception 1848 cookie = interact_2965(c, "http://www.acme.com/\xfc") 1849 1850 def test_mozilla(self): 1851 # Save / load Mozilla/Netscape cookie file format. 1852 year_plus_one = time.localtime()[0] + 1 1853 1854 filename = os_helper.TESTFN 1855 1856 c = MozillaCookieJar(filename, 1857 policy=DefaultCookiePolicy(rfc2965=True)) 1858 interact_2965(c, "http://www.acme.com/", 1859 "foo1=bar; max-age=100; Version=1") 1860 interact_2965(c, "http://www.acme.com/", 1861 'foo2=bar; port="80"; max-age=100; Discard; Version=1') 1862 interact_2965(c, "http://www.acme.com/", "foo3=bar; secure; Version=1") 1863 1864 expires = "expires=09-Nov-%d 23:12:40 GMT" % (year_plus_one,) 1865 interact_netscape(c, "http://www.foo.com/", 1866 "fooa=bar; %s" % expires) 1867 interact_netscape(c, "http://www.foo.com/", 1868 "foob=bar; Domain=.foo.com; %s" % expires) 1869 interact_netscape(c, "http://www.foo.com/", 1870 "fooc=bar; Domain=www.foo.com; %s" % expires) 1871 1872 for cookie in c: 1873 if cookie.name == "foo1": 1874 cookie.set_nonstandard_attr("HTTPOnly", "") 1875 1876 def save_and_restore(cj, ignore_discard): 1877 try: 1878 cj.save(ignore_discard=ignore_discard) 1879 new_c = MozillaCookieJar(filename, 1880 DefaultCookiePolicy(rfc2965=True)) 1881 new_c.load(ignore_discard=ignore_discard) 1882 finally: 1883 os_helper.unlink(filename) 1884 return new_c 1885 1886 new_c = save_and_restore(c, True) 1887 self.assertEqual(len(new_c), 6) # none discarded 1888 self.assertIn("name='foo1', value='bar'", repr(new_c)) 1889 self.assertIn("rest={'HTTPOnly': ''}", repr(new_c)) 1890 1891 new_c = save_and_restore(c, False) 1892 self.assertEqual(len(new_c), 4) # 2 of them discarded on save 1893 self.assertIn("name='foo1', value='bar'", repr(new_c)) 1894 1895 def test_netscape_misc(self): 1896 # Some additional Netscape cookies tests. 1897 c = CookieJar() 1898 headers = [] 1899 req = urllib.request.Request("http://foo.bar.acme.com/foo") 1900 1901 # Netscape allows a host part that contains dots 1902 headers.append("Set-Cookie: Customer=WILE_E_COYOTE; domain=.acme.com") 1903 res = FakeResponse(headers, "http://www.acme.com/foo") 1904 c.extract_cookies(res, req) 1905 1906 # and that the domain is the same as the host without adding a leading 1907 # dot to the domain. Should not quote even if strange chars are used 1908 # in the cookie value. 1909 headers.append("Set-Cookie: PART_NUMBER=3,4; domain=foo.bar.acme.com") 1910 res = FakeResponse(headers, "http://www.acme.com/foo") 1911 c.extract_cookies(res, req) 1912 1913 req = urllib.request.Request("http://foo.bar.acme.com/foo") 1914 c.add_cookie_header(req) 1915 self.assertIn("PART_NUMBER=3,4", req.get_header("Cookie")) 1916 self.assertIn("Customer=WILE_E_COYOTE",req.get_header("Cookie")) 1917 1918 def test_intranet_domains_2965(self): 1919 # Test handling of local intranet hostnames without a dot. 1920 c = CookieJar(DefaultCookiePolicy(rfc2965=True)) 1921 interact_2965(c, "http://example/", 1922 "foo1=bar; PORT; Discard; Version=1;") 1923 cookie = interact_2965(c, "http://example/", 1924 'foo2=bar; domain=".local"; Version=1') 1925 self.assertIn("foo1=bar", cookie) 1926 1927 interact_2965(c, "http://example/", 'foo3=bar; Version=1') 1928 cookie = interact_2965(c, "http://example/") 1929 self.assertIn("foo2=bar", cookie) 1930 self.assertEqual(len(c), 3) 1931 1932 def test_intranet_domains_ns(self): 1933 c = CookieJar(DefaultCookiePolicy(rfc2965 = False)) 1934 interact_netscape(c, "http://example/", "foo1=bar") 1935 cookie = interact_netscape(c, "http://example/", 1936 'foo2=bar; domain=.local') 1937 self.assertEqual(len(c), 2) 1938 self.assertIn("foo1=bar", cookie) 1939 1940 cookie = interact_netscape(c, "http://example/") 1941 self.assertIn("foo2=bar", cookie) 1942 self.assertEqual(len(c), 2) 1943 1944 def test_empty_path(self): 1945 # Test for empty path 1946 # Broken web-server ORION/1.3.38 returns to the client response like 1947 # 1948 # Set-Cookie: JSESSIONID=ABCDERANDOM123; Path= 1949 # 1950 # ie. with Path set to nothing. 1951 # In this case, extract_cookies() must set cookie to / (root) 1952 c = CookieJar(DefaultCookiePolicy(rfc2965 = True)) 1953 headers = [] 1954 1955 req = urllib.request.Request("http://www.ants.com/") 1956 headers.append("Set-Cookie: JSESSIONID=ABCDERANDOM123; Path=") 1957 res = FakeResponse(headers, "http://www.ants.com/") 1958 c.extract_cookies(res, req) 1959 1960 req = urllib.request.Request("http://www.ants.com/") 1961 c.add_cookie_header(req) 1962 1963 self.assertEqual(req.get_header("Cookie"), 1964 "JSESSIONID=ABCDERANDOM123") 1965 self.assertEqual(req.get_header("Cookie2"), '$Version="1"') 1966 1967 # missing path in the request URI 1968 req = urllib.request.Request("http://www.ants.com:8080") 1969 c.add_cookie_header(req) 1970 1971 self.assertEqual(req.get_header("Cookie"), 1972 "JSESSIONID=ABCDERANDOM123") 1973 self.assertEqual(req.get_header("Cookie2"), '$Version="1"') 1974 1975 def test_session_cookies(self): 1976 year_plus_one = time.localtime()[0] + 1 1977 1978 # Check session cookies are deleted properly by 1979 # CookieJar.clear_session_cookies method 1980 1981 req = urllib.request.Request('http://www.perlmeister.com/scripts') 1982 headers = [] 1983 headers.append("Set-Cookie: s1=session;Path=/scripts") 1984 headers.append("Set-Cookie: p1=perm; Domain=.perlmeister.com;" 1985 "Path=/;expires=Fri, 02-Feb-%d 23:24:20 GMT" % 1986 year_plus_one) 1987 headers.append("Set-Cookie: p2=perm;Path=/;expires=Fri, " 1988 "02-Feb-%d 23:24:20 GMT" % year_plus_one) 1989 headers.append("Set-Cookie: s2=session;Path=/scripts;" 1990 "Domain=.perlmeister.com") 1991 headers.append('Set-Cookie2: s3=session;Version=1;Discard;Path="/"') 1992 res = FakeResponse(headers, 'http://www.perlmeister.com/scripts') 1993 1994 c = CookieJar() 1995 c.extract_cookies(res, req) 1996 # How many session/permanent cookies do we have? 1997 counter = {"session_after": 0, 1998 "perm_after": 0, 1999 "session_before": 0, 2000 "perm_before": 0} 2001 for cookie in c: 2002 key = "%s_before" % cookie.value 2003 counter[key] = counter[key] + 1 2004 c.clear_session_cookies() 2005 # How many now? 2006 for cookie in c: 2007 key = "%s_after" % cookie.value 2008 counter[key] = counter[key] + 1 2009 2010 # a permanent cookie got lost accidentally 2011 self.assertEqual(counter["perm_after"], counter["perm_before"]) 2012 # a session cookie hasn't been cleared 2013 self.assertEqual(counter["session_after"], 0) 2014 # we didn't have session cookies in the first place 2015 self.assertNotEqual(counter["session_before"], 0) 2016 2017 2018if __name__ == "__main__": 2019 unittest.main() 2020