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