test_http_cookiejar.py 72.4 KB
Newer Older
1
"""Tests for http/cookiejar.py."""
2

3 4 5 6 7 8
import os
import re
import test.support
import time
import unittest
import urllib.request
9

10
from http.cookiejar import (time2isoz, http2time, iso2time, time2netscape,
11 12 13 14 15
     parse_ns_headers, join_header_words, split_header_words, Cookie,
     CookieJar, DefaultCookiePolicy, LWPCookieJar, MozillaCookieJar,
     LoadError, lwp_cookie_str, DEFAULT_HTTP_PORT, escape_path,
     reach, is_HDN, domain_match, user_domain_match, request_path,
     request_port, request_host)
16

17

18
class DateTimeTests(unittest.TestCase):
19 20 21 22

    def test_time2isoz(self):
        base = 1019227000
        day = 24*3600
23 24 25 26
        self.assertEqual(time2isoz(base), "2002-04-19 14:36:40Z")
        self.assertEqual(time2isoz(base+day), "2002-04-20 14:36:40Z")
        self.assertEqual(time2isoz(base+2*day), "2002-04-21 14:36:40Z")
        self.assertEqual(time2isoz(base+3*day), "2002-04-22 14:36:40Z")
27 28 29 30

        az = time2isoz()
        bz = time2isoz(500000)
        for text in (az, bz):
31 32
            self.assertRegex(text, r"^\d{4}-\d\d-\d\d \d\d:\d\d:\d\dZ$",
                             "bad time2isoz format: %s %s" % (az, bz))
33

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
    def test_time2netscape(self):
        base = 1019227000
        day = 24*3600
        self.assertEqual(time2netscape(base), "Fri, 19-Apr-2002 14:36:40 GMT")
        self.assertEqual(time2netscape(base+day),
                         "Sat, 20-Apr-2002 14:36:40 GMT")

        self.assertEqual(time2netscape(base+2*day),
                         "Sun, 21-Apr-2002 14:36:40 GMT")

        self.assertEqual(time2netscape(base+3*day),
                         "Mon, 22-Apr-2002 14:36:40 GMT")

        az = time2netscape()
        bz = time2netscape(500000)
        for text in (az, bz):
            # Format "%s, %02d-%s-%04d %02d:%02d:%02d GMT"
            self.assertRegex(
                text,
                r"[a-zA-Z]{3}, \d{2}-[a-zA-Z]{3}-\d{4} \d{2}:\d{2}:\d{2} GMT$",
                "bad time2netscape format: %s %s" % (az, bz))

56 57 58 59
    def test_http2time(self):
        def parse_date(text):
            return time.gmtime(http2time(text))[:6]

60
        self.assertEqual(parse_date("01 Jan 2001"), (2001, 1, 1, 0, 0, 0.0))
61 62

        # this test will break around year 2070
63
        self.assertEqual(parse_date("03-Feb-20"), (2020, 2, 3, 0, 0, 0.0))
64 65

        # this test will break around year 2048
66
        self.assertEqual(parse_date("03-Feb-98"), (1998, 2, 3, 0, 0, 0.0))
67 68 69 70 71 72 73 74 75 76 77 78 79 80

    def test_http2time_formats(self):
        # test http2time for supported dates.  Test cases with 2 digit year
        # will probably break in year 2044.
        tests = [
         'Thu, 03 Feb 1994 00:00:00 GMT',  # proposed new HTTP format
         'Thursday, 03-Feb-94 00:00:00 GMT',  # old rfc850 HTTP format
         'Thursday, 03-Feb-1994 00:00:00 GMT',  # broken rfc850 HTTP format

         '03 Feb 1994 00:00:00 GMT',  # HTTP format (no weekday)
         '03-Feb-94 00:00:00 GMT',  # old rfc850 (no weekday)
         '03-Feb-1994 00:00:00 GMT',  # broken rfc850 (no weekday)
         '03-Feb-1994 00:00 GMT',  # broken rfc850 (no weekday, no seconds)
         '03-Feb-1994 00:00',  # broken rfc850 (no weekday, no seconds, no tz)
81 82
         '02-Feb-1994 24:00',  # broken rfc850 (no weekday, no seconds,
                               # no tz) using hour 24 with yesterday date
83 84 85 86 87 88 89 90 91 92 93 94 95

         '03-Feb-94',  # old rfc850 HTTP format (no weekday, no time)
         '03-Feb-1994',  # broken rfc850 HTTP format (no weekday, no time)
         '03 Feb 1994',  # proposed new HTTP format (no weekday, no time)

         # A few tests with extra space at various places
         '  03   Feb   1994  0:00  ',
         '  03-Feb-1994  ',
        ]

        test_t = 760233600  # assume broken POSIX counting of seconds
        result = time2isoz(test_t)
        expected = "1994-02-03 00:00:00Z"
96 97
        self.assertEqual(result, expected,
                         "%s  =>  '%s' (%s)" % (test_t, result, expected))
98 99

        for s in tests:
100 101 102
            self.assertEqual(http2time(s), test_t, s)
            self.assertEqual(http2time(s.lower()), test_t, s.lower())
            self.assertEqual(http2time(s.upper()), test_t, s.upper())
103 104 105 106 107 108 109 110 111 112 113 114 115

    def test_http2time_garbage(self):
        for test in [
            '',
            'Garbage',
            'Mandag 16. September 1996',
            '01-00-1980',
            '01-13-1980',
            '00-01-1980',
            '32-01-1980',
            '01-01-1980 25:00:00',
            '01-01-1980 00:61:00',
            '01-01-1980 00:00:62',
116 117 118 119
            '08-Oct-3697739',
            '08-01-3697739',
            '09 Feb 19942632 22:23:32 GMT',
            'Wed, 09 Feb 1994834 22:23:32 GMT',
120
            ]:
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
            self.assertIsNone(http2time(test),
                              "http2time(%s) is not None\n"
                              "http2time(test) %s" % (test, http2time(test)))

    def test_iso2time(self):
        def parse_date(text):
            return time.gmtime(iso2time(text))[:6]

        # ISO 8601 compact format
        self.assertEqual(parse_date("19940203T141529Z"),
                         (1994, 2, 3, 14, 15, 29))

        # ISO 8601 with time behind UTC
        self.assertEqual(parse_date("1994-02-03 07:15:29 -0700"),
                         (1994, 2, 3, 14, 15, 29))

        # ISO 8601 with time ahead of UTC
        self.assertEqual(parse_date("1994-02-03 19:45:29 +0530"),
                         (1994, 2, 3, 14, 15, 29))

    def test_iso2time_formats(self):
        # test iso2time for supported dates.
        tests = [
            '1994-02-03 00:00:00 -0000', # ISO 8601 format
            '1994-02-03 00:00:00 +0000', # ISO 8601 format
            '1994-02-03 00:00:00',       # zone is optional
            '1994-02-03',                # only date
            '1994-02-03T00:00:00',       # Use T as separator
            '19940203',                  # only date
            '1994-02-02 24:00:00',       # using hour-24 yesterday date
            '19940203T000000Z',          # ISO 8601 compact format

            # A few tests with extra space at various places
            '  1994-02-03 ',
            '  1994-02-03T00:00:00  ',
        ]

        test_t = 760233600  # assume broken POSIX counting of seconds
        for s in tests:
160 161 162
            self.assertEqual(iso2time(s), test_t, s)
            self.assertEqual(iso2time(s.lower()), test_t, s.lower())
            self.assertEqual(iso2time(s.upper()), test_t, s.upper())
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

    def test_iso2time_garbage(self):
        for test in [
            '',
            'Garbage',
            'Thursday, 03-Feb-94 00:00:00 GMT',
            '1980-00-01',
            '1980-13-01',
            '1980-01-00',
            '1980-01-32',
            '1980-01-01 25:00:00',
            '1980-01-01 00:61:00',
            '01-01-1980 00:00:62',
            '01-01-1980T00:00:62',
            '19800101T250000Z'
            '1980-01-01 00:00:00 -2500',
            ]:
            self.assertIsNone(iso2time(test),
                              "iso2time(%s) is not None\n"
                              "iso2time(test) %s" % (test, iso2time(test)))
183 184


185
class HeaderTests(unittest.TestCase):
186

187 188
    def test_parse_ns_headers(self):
        # quotes should be stripped
189
        expected = [[('foo', 'bar'), ('expires', 2209069412), ('version', '0')]]
190
        for hdr in [
191 192
            'foo=bar; expires=01 Jan 2040 22:23:32 GMT',
            'foo=bar; expires="01 Jan 2040 22:23:32 GMT"',
193
            ]:
194
            self.assertEqual(parse_ns_headers([hdr]), expected)
195

196 197 198 199 200 201 202 203
    def test_parse_ns_headers_version(self):

        # quotes should be stripped
        expected = [[('foo', 'bar'), ('version', '1')]]
        for hdr in [
            'foo=bar; version="1"',
            'foo=bar; Version="1"',
            ]:
204
            self.assertEqual(parse_ns_headers([hdr]), expected)
205

206 207 208 209 210 211
    def test_parse_ns_headers_special_names(self):
        # names such as 'expires' are not special in first name=value pair
        # of Set-Cookie: header
        # Cookie with name 'expires'
        hdr = 'expires=01 Jan 2040 22:23:32 GMT'
        expected = [[("expires", "01 Jan 2040 22:23:32 GMT"), ("version", "0")]]
212
        self.assertEqual(parse_ns_headers([hdr]), expected)
213

214 215
    def test_join_header_words(self):
        joined = join_header_words([[("foo", None), ("bar", "baz")]])
216
        self.assertEqual(joined, "foo; bar=baz")
217

218
        self.assertEqual(join_header_words([[]]), "")
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243

    def test_split_header_words(self):
        tests = [
            ("foo", [[("foo", None)]]),
            ("foo=bar", [[("foo", "bar")]]),
            ("   foo   ", [[("foo", None)]]),
            ("   foo=   ", [[("foo", "")]]),
            ("   foo=", [[("foo", "")]]),
            ("   foo=   ; ", [[("foo", "")]]),
            ("   foo=   ; bar= baz ", [[("foo", ""), ("bar", "baz")]]),
            ("foo=bar bar=baz", [[("foo", "bar"), ("bar", "baz")]]),
            # doesn't really matter if this next fails, but it works ATM
            ("foo= bar=baz", [[("foo", "bar=baz")]]),
            ("foo=bar;bar=baz", [[("foo", "bar"), ("bar", "baz")]]),
            ('foo bar baz', [[("foo", None), ("bar", None), ("baz", None)]]),
            ("a, b, c", [[("a", None)], [("b", None)], [("c", None)]]),
            (r'foo; bar=baz, spam=, foo="\,\;\"", bar= ',
             [[("foo", None), ("bar", "baz")],
              [("spam", "")], [("foo", ',;"')], [("bar", "")]]),
            ]

        for arg, expect in tests:
            try:
                result = split_header_words([arg])
            except:
244 245
                import traceback, io
                f = io.StringIO()
246 247
                traceback.print_exc(None, f)
                result = "(error -- traceback follows)\n\n%s" % f.getvalue()
248
            self.assertEqual(result,  expect, """
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
When parsing: '%s'
Expected:     '%s'
Got:          '%s'
""" % (arg, expect, result))

    def test_roundtrip(self):
        tests = [
            ("foo", "foo"),
            ("foo=bar", "foo=bar"),
            ("   foo   ", "foo"),
            ("foo=", 'foo=""'),
            ("foo=bar bar=baz", "foo=bar; bar=baz"),
            ("foo=bar;bar=baz", "foo=bar; bar=baz"),
            ('foo bar baz', "foo; bar; baz"),
            (r'foo="\"" bar="\\"', r'foo="\""; bar="\\"'),
            ('foo,,,bar', 'foo, bar'),
            ('foo=bar,bar=baz', 'foo=bar, bar=baz'),

            ('text/html; charset=iso-8859-1',
             'text/html; charset="iso-8859-1"'),

            ('foo="bar"; port="80,81"; discard, bar=baz',
             'foo=bar; port="80,81"; discard, bar=baz'),

            (r'Basic realm="\"foo\\\\bar\""',
             r'Basic; realm="\"foo\\\\bar\""')
            ]

        for arg, expect in tests:
            input = split_header_words([arg])
            res = join_header_words(input)
280
            self.assertEqual(res, expect, """
281 282 283 284 285 286 287 288 289 290 291 292
When parsing: '%s'
Expected:     '%s'
Got:          '%s'
Input was:    '%s'
""" % (arg, expect, res, input))


class FakeResponse:
    def __init__(self, headers=[], url=None):
        """
        headers: list of RFC822-style 'Key: value' strings
        """
293 294
        import email
        self._headers = email.message_from_string("\n".join(headers))
295 296 297 298 299 300 301 302 303 304 305
        self._url = url
    def info(self): return self._headers

def interact_2965(cookiejar, url, *set_cookie_hdrs):
    return _interact(cookiejar, url, set_cookie_hdrs, "Set-Cookie2")

def interact_netscape(cookiejar, url, *set_cookie_hdrs):
    return _interact(cookiejar, url, set_cookie_hdrs, "Set-Cookie")

def _interact(cookiejar, url, set_cookie_hdrs, hdr_name):
    """Perform a single request / response cycle, returning Cookie: header."""
306
    req = urllib.request.Request(url)
307 308 309 310 311 312 313 314 315 316
    cookiejar.add_cookie_header(req)
    cookie_hdr = req.get_header("Cookie", "")
    headers = []
    for hdr in set_cookie_hdrs:
        headers.append("%s: %s" % (hdr_name, hdr))
    res = FakeResponse(headers, url)
    cookiejar.extract_cookies(res, req)
    return cookie_hdr


317
class FileCookieJarTests(unittest.TestCase):
318 319
    def test_lwp_valueless_cookie(self):
        # cookies with no value should be saved and loaded consistently
320
        filename = test.support.TESTFN
321 322 323 324 325 326 327 328 329 330 331 332
        c = LWPCookieJar()
        interact_netscape(c, "http://www.acme.com/", 'boo')
        self.assertEqual(c._cookies["www.acme.com"]["/"]["boo"].value, None)
        try:
            c.save(filename, ignore_discard=True)
            c = LWPCookieJar()
            c.load(filename, ignore_discard=True)
        finally:
            try: os.unlink(filename)
            except OSError: pass
        self.assertEqual(c._cookies["www.acme.com"]["/"]["boo"].value, None)

333
    def test_bad_magic(self):
334
        # OSErrors (eg. file doesn't exist) are allowed to propagate
335
        filename = test.support.TESTFN
336 337 338 339 340
        for cookiejar_class in LWPCookieJar, MozillaCookieJar:
            c = cookiejar_class()
            try:
                c.load(filename="for this test to work, a file with this "
                                "filename should not exist")
341 342 343 344
            except OSError as exc:
                # an OSError subclass (likely FileNotFoundError), but not
                # LoadError
                self.assertIsNot(exc.__class__, LoadError)
345
            else:
346
                self.fail("expected OSError for invalid filename")
347 348 349
        # Invalid contents of cookies file (eg. bad magic string)
        # causes a LoadError.
        try:
350 351 352 353 354
            with open(filename, "w") as f:
                f.write("oops\n")
                for cookiejar_class in LWPCookieJar, MozillaCookieJar:
                    c = cookiejar_class()
                    self.assertRaises(LoadError, c.load, filename)
355 356 357
        finally:
            try: os.unlink(filename)
            except OSError: pass
358

359
class CookieTests(unittest.TestCase):
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
    # XXX
    # Get rid of string comparisons where not actually testing str / repr.
    # .clear() etc.
    # IP addresses like 50 (single number, no dot) and domain-matching
    #  functions (and is_HDN)?  See draft RFC 2965 errata.
    # Strictness switches
    # is_third_party()
    # unverifiability / third-party blocking
    # Netscape cookies work the same as RFC 2965 with regard to port.
    # Set-Cookie with negative max age.
    # If turn RFC 2965 handling off, Set-Cookie2 cookies should not clobber
    #  Set-Cookie cookies.
    # Cookie2 should be sent if *any* cookies are not V1 (ie. V0 OR V2 etc.).
    # Cookies (V1 and V0) with no expiry date should be set to be discarded.
    # RFC 2965 Quoting:
    #  Should accept unquoted cookie-attribute values?  check errata draft.
    #   Which are required on the way in and out?
    #  Should always return quoted cookie-attribute values?
    # Proper testing of when RFC 2965 clobbers Netscape (waiting for errata).
    # Path-match on return (same for V0 and V1).
    # RFC 2965 acceptance and returning rules
    #  Set-Cookie2 without version attribute is rejected.

    # Netscape peculiarities list from Ronald Tschalar.
    # The first two still need tests, the rest are covered.
## - Quoting: only quotes around the expires value are recognized as such
##   (and yes, some folks quote the expires value); quotes around any other
##   value are treated as part of the value.
## - White space: white space around names and values is ignored
## - Default path: if no path parameter is given, the path defaults to the
##   path in the request-uri up to, but not including, the last '/'. Note
##   that this is entirely different from what the spec says.
## - Commas and other delimiters: Netscape just parses until the next ';'.
##   This means it will allow commas etc inside values (and yes, both
##   commas and equals are commonly appear in the cookie value). This also
##   means that if you fold multiple Set-Cookie header fields into one,
##   comma-separated list, it'll be a headache to parse (at least my head
397
##   starts hurting every time I think of that code).
398
## - Expires: You'll get all sorts of date formats in the expires,
399
##   including empty expires attributes ("expires="). Be as flexible as you
400 401 402 403 404 405 406 407 408 409 410 411
##   can, and certainly don't expect the weekday to be there; if you can't
##   parse it, just ignore it and pretend it's a session cookie.
## - Domain-matching: Netscape uses the 2-dot rule for _all_ domains, not
##   just the 7 special TLD's listed in their spec. And folks rely on
##   that...

    def test_domain_return_ok(self):
        # test optimization: .domain_return_ok() should filter out most
        # domains in the CookieJar before we try to access them (because that
        # may require disk access -- in particular, with MSIECookieJar)
        # This is only a rough check for performance reasons, so it's not too
        # critical as long as it's sufficiently liberal.
412
        pol = DefaultCookiePolicy()
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
        for url, domain, ok in [
            ("http://foo.bar.com/", "blah.com", False),
            ("http://foo.bar.com/", "rhubarb.blah.com", False),
            ("http://foo.bar.com/", "rhubarb.foo.bar.com", False),
            ("http://foo.bar.com/", ".foo.bar.com", True),
            ("http://foo.bar.com/", "foo.bar.com", True),
            ("http://foo.bar.com/", ".bar.com", True),
            ("http://foo.bar.com/", "com", True),
            ("http://foo.com/", "rhubarb.foo.com", False),
            ("http://foo.com/", ".foo.com", True),
            ("http://foo.com/", "foo.com", True),
            ("http://foo.com/", "com", True),
            ("http://foo/", "rhubarb.foo", False),
            ("http://foo/", ".foo", True),
            ("http://foo/", "foo", True),
            ("http://foo/", "foo.local", True),
            ("http://foo/", ".local", True),
            ]:
431
            request = urllib.request.Request(url)
432
            r = pol.domain_return_ok(domain, request)
433
            if ok: self.assertTrue(r)
434
            else: self.assertFalse(r)
435 436 437

    def test_missing_value(self):
        # missing = sign in Cookie: header is regarded by Mozilla as a missing
438
        # name, and by http.cookiejar as a missing value
439
        filename = test.support.TESTFN
440 441 442 443
        c = MozillaCookieJar(filename)
        interact_netscape(c, "http://www.acme.com/", 'eggs')
        interact_netscape(c, "http://www.acme.com/", '"spam"; path=/foo/')
        cookie = c._cookies["www.acme.com"]["/"]["eggs"]
444
        self.assertIsNone(cookie.value)
445
        self.assertEqual(cookie.name, "eggs")
446
        cookie = c._cookies["www.acme.com"]['/foo/']['"spam"']
447
        self.assertIsNone(cookie.value)
448 449
        self.assertEqual(cookie.name, '"spam"')
        self.assertEqual(lwp_cookie_str(cookie), (
450 451 452 453 454 455 456 457 458 459
            r'"spam"; path="/foo/"; domain="www.acme.com"; '
            'path_spec; discard; version=0'))
        old_str = repr(c)
        c.save(ignore_expires=True, ignore_discard=True)
        try:
            c = MozillaCookieJar(filename)
            c.revert(ignore_expires=True, ignore_discard=True)
        finally:
            os.unlink(c.filename)
        # cookies unchanged apart from lost info re. whether path was specified
460
        self.assertEqual(
461 462 463 464
            repr(c),
            re.sub("path_specified=%s" % True, "path_specified=%s" % False,
                   old_str)
            )
465 466
        self.assertEqual(interact_netscape(c, "http://www.acme.com/foo/"),
                         '"spam"; eggs')
467

468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
    def test_rfc2109_handling(self):
        # RFC 2109 cookies are handled as RFC 2965 or Netscape cookies,
        # dependent on policy settings
        for rfc2109_as_netscape, rfc2965, version in [
            # default according to rfc2965 if not explicitly specified
            (None, False, 0),
            (None, True, 1),
            # explicit rfc2109_as_netscape
            (False, False, None),  # version None here means no cookie stored
            (False, True, 1),
            (True, False, 0),
            (True, True, 0),
            ]:
            policy = DefaultCookiePolicy(
                rfc2109_as_netscape=rfc2109_as_netscape,
                rfc2965=rfc2965)
            c = CookieJar(policy)
            interact_netscape(c, "http://www.example.com/", "ni=ni; Version=1")
            try:
                cookie = c._cookies["www.example.com"]["/"]["ni"]
            except KeyError:
489
                self.assertIsNone(version)  # didn't expect a stored cookie
490 491 492 493 494 495 496 497 498
            else:
                self.assertEqual(cookie.version, version)
                # 2965 cookies are unaffected
                interact_2965(c, "http://www.example.com/",
                              "foo=bar; Version=1")
                if rfc2965:
                    cookie2965 = c._cookies["www.example.com"]["/"]["foo"]
                    self.assertEqual(cookie2965.version, 1)

499 500 501 502 503 504 505 506 507
    def test_ns_parser(self):
        c = CookieJar()
        interact_netscape(c, "http://www.acme.com/",
                          'spam=eggs; DoMain=.acme.com; port; blArgh="feep"')
        interact_netscape(c, "http://www.acme.com/", 'ni=ni; port=80,8080')
        interact_netscape(c, "http://www.acme.com:80/", 'nini=ni')
        interact_netscape(c, "http://www.acme.com:80/", 'foo=bar; expires=')
        interact_netscape(c, "http://www.acme.com:80/", 'spam=eggs; '
                          'expires="Foo Bar 25 33:22:11 3022"')
508 509 510
        interact_netscape(c, 'http://www.acme.com/', 'fortytwo=')
        interact_netscape(c, 'http://www.acme.com/', '=unladenswallow')
        interact_netscape(c, 'http://www.acme.com/', 'holyhandgrenade')
511 512

        cookie = c._cookies[".acme.com"]["/"]["spam"]
513
        self.assertEqual(cookie.domain, ".acme.com")
514
        self.assertTrue(cookie.domain_specified)
515
        self.assertEqual(cookie.port, DEFAULT_HTTP_PORT)
516
        self.assertFalse(cookie.port_specified)
517
        # case is preserved
518 519
        self.assertTrue(cookie.has_nonstandard_attr("blArgh"))
        self.assertFalse(cookie.has_nonstandard_attr("blargh"))
520 521

        cookie = c._cookies["www.acme.com"]["/"]["ni"]
522
        self.assertEqual(cookie.domain, "www.acme.com")
523
        self.assertFalse(cookie.domain_specified)
524
        self.assertEqual(cookie.port, "80,8080")
525
        self.assertTrue(cookie.port_specified)
526 527

        cookie = c._cookies["www.acme.com"]["/"]["nini"]
528 529
        self.assertIsNone(cookie.port)
        self.assertFalse(cookie.port_specified)
530 531 532 533

        # invalid expires should not cause cookie to be dropped
        foo = c._cookies["www.acme.com"]["/"]["foo"]
        spam = c._cookies["www.acme.com"]["/"]["foo"]
534 535
        self.assertIsNone(foo.expires)
        self.assertIsNone(spam.expires)
536

537 538 539 540 541 542 543 544 545 546
        cookie = c._cookies['www.acme.com']['/']['fortytwo']
        self.assertIsNotNone(cookie.value)
        self.assertEqual(cookie.value, '')

        # there should be a distinction between a present but empty value
        # (above) and a value that's entirely missing (below)

        cookie = c._cookies['www.acme.com']['/']['holyhandgrenade']
        self.assertIsNone(cookie.value)

547 548 549 550 551 552 553 554
    def test_ns_parser_special_names(self):
        # names such as 'expires' are not special in first name=value pair
        # of Set-Cookie: header
        c = CookieJar()
        interact_netscape(c, "http://www.acme.com/", 'expires=eggs')
        interact_netscape(c, "http://www.acme.com/", 'version=eggs; spam=eggs')

        cookies = c._cookies["www.acme.com"]["/"]
555 556
        self.assertIn('expires', cookies)
        self.assertIn('version', cookies)
557

558 559 560 561 562 563
    def test_expires(self):
        # if expires is in future, keep cookie...
        c = CookieJar()
        future = time2netscape(time.time()+3600)
        interact_netscape(c, "http://www.acme.com/", 'spam="bar"; expires=%s' %
                          future)
564
        self.assertEqual(len(c), 1)
565 566 567 568 569
        now = time2netscape(time.time()-1)
        # ... and if in past or present, discard it
        interact_netscape(c, "http://www.acme.com/", 'foo="eggs"; expires=%s' %
                          now)
        h = interact_netscape(c, "http://www.acme.com/")
570
        self.assertEqual(len(c), 1)
571 572
        self.assertIn('spam="bar"', h)
        self.assertNotIn("foo", h)
573 574 575 576 577 578 579

        # max-age takes precedence over expires, and zero max-age is request to
        # delete both new cookie and any old matching cookie
        interact_netscape(c, "http://www.acme.com/", 'eggs="bar"; expires=%s' %
                          future)
        interact_netscape(c, "http://www.acme.com/", 'bar="bar"; expires=%s' %
                          future)
580
        self.assertEqual(len(c), 3)
581 582 583 584 585
        interact_netscape(c, "http://www.acme.com/", 'eggs="bar"; '
                          'expires=%s; max-age=0' % future)
        interact_netscape(c, "http://www.acme.com/", 'bar="bar"; '
                          'max-age=0; expires=%s' % future)
        h = interact_netscape(c, "http://www.acme.com/")
586
        self.assertEqual(len(c), 1)
587 588 589

        # test expiry at end of session for cookies with no expires attribute
        interact_netscape(c, "http://www.rhubarb.net/", 'whum="fizz"')
590
        self.assertEqual(len(c), 2)
591
        c.clear_session_cookies()
592
        self.assertEqual(len(c), 1)
593
        self.assertIn('spam="bar"', h)
594

595 596 597 598 599 600 601 602 603
        # test if fractional expiry is accepted
        cookie  = Cookie(0, "name", "value",
                         None, False, "www.python.org",
                         True, False, "/",
                         False, False, "1444312383.018307",
                         False, None, None,
                         {})
        self.assertEqual(cookie.expires, 1444312383)

604 605 606 607 608 609 610 611
        # XXX RFC 2965 expiry rules (some apply to V0 too)

    def test_default_path(self):
        # RFC 2965
        pol = DefaultCookiePolicy(rfc2965=True)

        c = CookieJar(pol)
        interact_2965(c, "http://www.acme.com/", 'spam="bar"; Version="1"')
612
        self.assertIn("/", c._cookies["www.acme.com"])
613 614 615

        c = CookieJar(pol)
        interact_2965(c, "http://www.acme.com/blah", 'eggs="bar"; Version="1"')
616
        self.assertIn("/", c._cookies["www.acme.com"])
617 618 619 620

        c = CookieJar(pol)
        interact_2965(c, "http://www.acme.com/blah/rhubarb",
                      'eggs="bar"; Version="1"')
621
        self.assertIn("/blah/", c._cookies["www.acme.com"])
622 623 624 625

        c = CookieJar(pol)
        interact_2965(c, "http://www.acme.com/blah/rhubarb/",
                      'eggs="bar"; Version="1"')
626
        self.assertIn("/blah/rhubarb/", c._cookies["www.acme.com"])
627 628 629 630 631

        # Netscape

        c = CookieJar()
        interact_netscape(c, "http://www.acme.com/", 'spam="bar"')
632
        self.assertIn("/", c._cookies["www.acme.com"])
633 634 635

        c = CookieJar()
        interact_netscape(c, "http://www.acme.com/blah", 'eggs="bar"')
636
        self.assertIn("/", c._cookies["www.acme.com"])
637 638 639

        c = CookieJar()
        interact_netscape(c, "http://www.acme.com/blah/rhubarb", 'eggs="bar"')
640
        self.assertIn("/blah", c._cookies["www.acme.com"])
641 642 643

        c = CookieJar()
        interact_netscape(c, "http://www.acme.com/blah/rhubarb/", 'eggs="bar"')
644
        self.assertIn("/blah/rhubarb", c._cookies["www.acme.com"])
645

646 647 648 649 650 651 652 653
    def test_default_path_with_query(self):
        cj = CookieJar()
        uri = "http://example.com/?spam/eggs"
        value = 'eggs="bar"'
        interact_netscape(cj, uri, value)
        # Default path does not include query, so is "/", not "/?spam".
        self.assertIn("/", cj._cookies["example.com"])
        # Cookie is sent back to the same URI.
654
        self.assertEqual(interact_netscape(cj, uri), value)
655

656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    def test_escape_path(self):
        cases = [
            # quoted safe
            ("/foo%2f/bar", "/foo%2F/bar"),
            ("/foo%2F/bar", "/foo%2F/bar"),
            # quoted %
            ("/foo%%/bar", "/foo%%/bar"),
            # quoted unsafe
            ("/fo%19o/bar", "/fo%19o/bar"),
            ("/fo%7do/bar", "/fo%7Do/bar"),
            # unquoted safe
            ("/foo/bar&", "/foo/bar&"),
            ("/foo//bar", "/foo//bar"),
            ("\176/foo/bar", "\176/foo/bar"),
            # unquoted unsafe
            ("/foo\031/bar", "/foo%19/bar"),
            ("/\175foo/bar", "/%7Dfoo/bar"),
673 674
            # unicode, latin-1 range
            ("/foo/bar\u00fc", "/foo/bar%C3%BC"),     # UTF-8 encoded
675
            # unicode
676
            ("/foo/bar\uabcd", "/foo/bar%EA%AF%8D"),  # UTF-8 encoded
677 678
            ]
        for arg, result in cases:
679
            self.assertEqual(escape_path(arg), result)
680 681 682

    def test_request_path(self):
        # with parameters
683
        req = urllib.request.Request(
684
            "http://www.example.com/rheum/rhaponticum;"
685
            "foo=bar;sing=song?apples=pears&spam=eggs#ni")
686 687
        self.assertEqual(request_path(req),
                         "/rheum/rhaponticum;foo=bar;sing=song")
688
        # without parameters
689
        req = urllib.request.Request(
690
            "http://www.example.com/rheum/rhaponticum?"
691
            "apples=pears&spam=eggs#ni")
692
        self.assertEqual(request_path(req), "/rheum/rhaponticum")
693
        # missing final slash
694
        req = urllib.request.Request("http://www.example.com")
695
        self.assertEqual(request_path(req), "/")
696 697

    def test_request_port(self):
698 699
        req = urllib.request.Request("http://www.acme.com:1234/",
                                     headers={"Host": "www.acme.com:4321"})
700
        self.assertEqual(request_port(req), "1234")
701 702
        req = urllib.request.Request("http://www.acme.com/",
                                     headers={"Host": "www.acme.com:4321"})
703
        self.assertEqual(request_port(req), DEFAULT_HTTP_PORT)
704 705 706

    def test_request_host(self):
        # this request is illegal (RFC2616, 14.2.3)
707 708
        req = urllib.request.Request("http://1.1.1.1/",
                                     headers={"Host": "www.acme.com:80"})
709 710
        # libwww-perl wants this response, but that seems wrong (RFC 2616,
        # section 5.2, point 1., and RFC 2965 section 1, paragraph 3)
711 712
        #self.assertEqual(request_host(req), "www.acme.com")
        self.assertEqual(request_host(req), "1.1.1.1")
713 714
        req = urllib.request.Request("http://www.acme.com/",
                                     headers={"Host": "irrelevant.com"})
715
        self.assertEqual(request_host(req), "www.acme.com")
716
        # port shouldn't be in request-host
717 718
        req = urllib.request.Request("http://www.acme.com:2345/resource.html",
                                     headers={"Host": "www.acme.com:5432"})
719
        self.assertEqual(request_host(req), "www.acme.com")
720 721

    def test_is_HDN(self):
722 723
        self.assertTrue(is_HDN("foo.bar.com"))
        self.assertTrue(is_HDN("1foo2.3bar4.5com"))
724 725 726 727 728 729
        self.assertFalse(is_HDN("192.168.1.1"))
        self.assertFalse(is_HDN(""))
        self.assertFalse(is_HDN("."))
        self.assertFalse(is_HDN(".foo.bar.com"))
        self.assertFalse(is_HDN("..foo"))
        self.assertFalse(is_HDN("foo."))
730 731

    def test_reach(self):
732 733 734 735 736 737 738 739
        self.assertEqual(reach("www.acme.com"), ".acme.com")
        self.assertEqual(reach("acme.com"), "acme.com")
        self.assertEqual(reach("acme.local"), ".local")
        self.assertEqual(reach(".local"), ".local")
        self.assertEqual(reach(".com"), ".com")
        self.assertEqual(reach("."), ".")
        self.assertEqual(reach(""), "")
        self.assertEqual(reach("192.168.0.1"), "192.168.0.1")
740 741

    def test_domain_match(self):
742
        self.assertTrue(domain_match("192.168.1.1", "192.168.1.1"))
743
        self.assertFalse(domain_match("192.168.1.1", ".168.1.1"))
744 745
        self.assertTrue(domain_match("x.y.com", "x.Y.com"))
        self.assertTrue(domain_match("x.y.com", ".Y.com"))
746
        self.assertFalse(domain_match("x.y.com", "Y.com"))
747
        self.assertTrue(domain_match("a.b.c.com", ".c.com"))
748
        self.assertFalse(domain_match(".c.com", "a.b.c.com"))
749
        self.assertTrue(domain_match("example.local", ".local"))
750 751
        self.assertFalse(domain_match("blah.blah", ""))
        self.assertFalse(domain_match("", ".rhubarb.rhubarb"))
752 753 754
        self.assertTrue(domain_match("", ""))

        self.assertTrue(user_domain_match("acme.com", "acme.com"))
755
        self.assertFalse(user_domain_match("acme.com", ".acme.com"))
756 757 758 759
        self.assertTrue(user_domain_match("rhubarb.acme.com", ".acme.com"))
        self.assertTrue(user_domain_match("www.rhubarb.acme.com", ".acme.com"))
        self.assertTrue(user_domain_match("x.y.com", "x.Y.com"))
        self.assertTrue(user_domain_match("x.y.com", ".Y.com"))
760
        self.assertFalse(user_domain_match("x.y.com", "Y.com"))
761
        self.assertTrue(user_domain_match("y.com", "Y.com"))
762
        self.assertFalse(user_domain_match(".y.com", "Y.com"))
763 764
        self.assertTrue(user_domain_match(".y.com", ".Y.com"))
        self.assertTrue(user_domain_match("x.y.com", ".com"))
765 766 767 768 769
        self.assertFalse(user_domain_match("x.y.com", "com"))
        self.assertFalse(user_domain_match("x.y.com", "m"))
        self.assertFalse(user_domain_match("x.y.com", ".m"))
        self.assertFalse(user_domain_match("x.y.com", ""))
        self.assertFalse(user_domain_match("x.y.com", "."))
770
        self.assertTrue(user_domain_match("192.168.1.1", "192.168.1.1"))
771
        # not both HDNs, so must string-compare equal to match
772 773
        self.assertFalse(user_domain_match("192.168.1.1", ".168.1.1"))
        self.assertFalse(user_domain_match("192.168.1.1", "."))
774
        # empty string is a special case
775
        self.assertFalse(user_domain_match("192.168.1.1", ""))
776 777 778 779 780 781 782 783 784

    def test_wrong_domain(self):
        # Cookies whose effective request-host name does not domain-match the
        # domain are rejected.

        # XXX far from complete
        c = CookieJar()
        interact_2965(c, "http://www.nasty.com/",
                      'foo=bar; domain=friendly.org; Version="1"')
785
        self.assertEqual(len(c), 0)
786

787 788 789 790 791 792 793 794
    def test_strict_domain(self):
        # Cookies whose domain is a country-code tld like .co.uk should
        # not be set if CookiePolicy.strict_domain is true.
        cp = DefaultCookiePolicy(strict_domain=True)
        cj = CookieJar(policy=cp)
        interact_netscape(cj, "http://example.co.uk/", 'no=problemo')
        interact_netscape(cj, "http://example.co.uk/",
                          'okey=dokey; Domain=.example.co.uk')
795
        self.assertEqual(len(cj), 2)
796 797 798
        for pseudo_tld in [".co.uk", ".org.za", ".tx.us", ".name.us"]:
            interact_netscape(cj, "http://example.%s/" % pseudo_tld,
                              'spam=eggs; Domain=.co.uk')
799
            self.assertEqual(len(cj), 2)
800

801 802 803 804 805 806 807 808
    def test_two_component_domain_ns(self):
        # Netscape: .www.bar.com, www.bar.com, .bar.com, bar.com, no domain
        # should all get accepted, as should .acme.com, acme.com and no domain
        # for 2-component domains like acme.com.
        c = CookieJar()

        # two-component V0 domain is OK
        interact_netscape(c, "http://foo.net/", 'ns=bar')
809 810 811
        self.assertEqual(len(c), 1)
        self.assertEqual(c._cookies["foo.net"]["/"]["ns"].value, "bar")
        self.assertEqual(interact_netscape(c, "http://foo.net/"), "ns=bar")
812
        # *will* be returned to any other domain (unlike RFC 2965)...
813 814
        self.assertEqual(interact_netscape(c, "http://www.foo.net/"),
                         "ns=bar")
815 816 817 818
        # ...unless requested otherwise
        pol = DefaultCookiePolicy(
            strict_ns_domain=DefaultCookiePolicy.DomainStrictNonDomain)
        c.set_policy(pol)
819
        self.assertEqual(interact_netscape(c, "http://www.foo.net/"), "")
820 821 822 823 824 825 826 827

        # unlike RFC 2965, even explicit two-component domain is OK,
        # because .foo.net matches foo.net
        interact_netscape(c, "http://foo.net/foo/",
                          'spam1=eggs; domain=foo.net')
        # even if starts with a dot -- in NS rules, .foo.net matches foo.net!
        interact_netscape(c, "http://foo.net/foo/bar/",
                          'spam2=eggs; domain=.foo.net')
828 829 830 831 832 833 834
        self.assertEqual(len(c), 3)
        self.assertEqual(c._cookies[".foo.net"]["/foo"]["spam1"].value,
                         "eggs")
        self.assertEqual(c._cookies[".foo.net"]["/foo/bar"]["spam2"].value,
                         "eggs")
        self.assertEqual(interact_netscape(c, "http://foo.net/foo/bar/"),
                         "spam2=eggs; spam1=eggs; ns=bar")
835 836 837

        # top-level domain is too general
        interact_netscape(c, "http://foo.net/", 'nini="ni"; domain=.net')
838
        self.assertEqual(len(c), 3)
839 840 841 842 843 844 845

##         # Netscape protocol doesn't allow non-special top level domains (such
##         # as co.uk) in the domain attribute unless there are at least three
##         # dots in it.
        # Oh yes it does!  Real implementations don't check this, and real
        # cookies (of course) rely on that behaviour.
        interact_netscape(c, "http://foo.co.uk", 'nasty=trick; domain=.co.uk')
846 847
##         self.assertEqual(len(c), 2)
        self.assertEqual(len(c), 4)
848 849 850 851 852 853 854

    def test_two_component_domain_rfc2965(self):
        pol = DefaultCookiePolicy(rfc2965=True)
        c = CookieJar(pol)

        # two-component V1 domain is OK
        interact_2965(c, "http://foo.net/", 'foo=bar; Version="1"')
855 856 857 858
        self.assertEqual(len(c), 1)
        self.assertEqual(c._cookies["foo.net"]["/"]["foo"].value, "bar")
        self.assertEqual(interact_2965(c, "http://foo.net/"),
                         "$Version=1; foo=bar")
859
        # won't be returned to any other domain (because domain was implied)
860
        self.assertEqual(interact_2965(c, "http://www.foo.net/"), "")
861 862 863 864 865 866

        # unless domain is given explicitly, because then it must be
        # rewritten to start with a dot: foo.net --> .foo.net, which does
        # not domain-match foo.net
        interact_2965(c, "http://foo.net/foo",
                      'spam=eggs; domain=foo.net; path=/foo; Version="1"')
867 868 869
        self.assertEqual(len(c), 1)
        self.assertEqual(interact_2965(c, "http://foo.net/foo"),
                         "$Version=1; foo=bar")
870 871 872 873 874

        # explicit foo.net from three-component domain www.foo.net *does* get
        # set, because .foo.net domain-matches .foo.net
        interact_2965(c, "http://www.foo.net/foo/",
                      'spam=eggs; domain=foo.net; Version="1"')
875 876 877 878 879 880 881
        self.assertEqual(c._cookies[".foo.net"]["/foo/"]["spam"].value,
                         "eggs")
        self.assertEqual(len(c), 2)
        self.assertEqual(interact_2965(c, "http://foo.net/foo/"),
                         "$Version=1; foo=bar")
        self.assertEqual(interact_2965(c, "http://www.foo.net/foo/"),
                         '$Version=1; spam=eggs; $Domain="foo.net"')
882 883 884 885

        # top-level domain is too general
        interact_2965(c, "http://foo.net/",
                      'ni="ni"; domain=".net"; Version="1"')
886
        self.assertEqual(len(c), 2)
887 888 889 890

        # RFC 2965 doesn't require blocking this
        interact_2965(c, "http://foo.co.uk/",
                      'nasty=trick; domain=.co.uk; Version="1"')
891
        self.assertEqual(len(c), 3)
892 893 894 895 896 897

    def test_domain_allow(self):
        c = CookieJar(policy=DefaultCookiePolicy(
            blocked_domains=["acme.com"],
            allowed_domains=["www.acme.com"]))

898
        req = urllib.request.Request("http://acme.com/")
899 900 901
        headers = ["Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/"]
        res = FakeResponse(headers, "http://acme.com/")
        c.extract_cookies(res, req)
902
        self.assertEqual(len(c), 0)
903

904
        req = urllib.request.Request("http://www.acme.com/")
905 906
        res = FakeResponse(headers, "http://www.acme.com/")
        c.extract_cookies(res, req)
907
        self.assertEqual(len(c), 1)
908

909
        req = urllib.request.Request("http://www.coyote.com/")
910 911
        res = FakeResponse(headers, "http://www.coyote.com/")
        c.extract_cookies(res, req)
912
        self.assertEqual(len(c), 1)
913 914

        # set a cookie with non-allowed domain...
915
        req = urllib.request.Request("http://www.coyote.com/")
916 917 918
        res = FakeResponse(headers, "http://www.coyote.com/")
        cookies = c.make_cookies(res, req)
        c.set_cookie(cookies[0])
919
        self.assertEqual(len(c), 2)
920 921
        # ... and check is doesn't get returned
        c.add_cookie_header(req)
922
        self.assertFalse(req.has_header("Cookie"))
923 924 925 926 927 928 929

    def test_domain_block(self):
        pol = DefaultCookiePolicy(
            rfc2965=True, blocked_domains=[".acme.com"])
        c = CookieJar(policy=pol)
        headers = ["Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/"]

930
        req = urllib.request.Request("http://www.acme.com/")
931 932
        res = FakeResponse(headers, "http://www.acme.com/")
        c.extract_cookies(res, req)
933
        self.assertEqual(len(c), 0)
934 935 936

        p = pol.set_blocked_domains(["acme.com"])
        c.extract_cookies(res, req)
937
        self.assertEqual(len(c), 1)
938 939

        c.clear()
940
        req = urllib.request.Request("http://www.roadrunner.net/")
941 942
        res = FakeResponse(headers, "http://www.roadrunner.net/")
        c.extract_cookies(res, req)
943
        self.assertEqual(len(c), 1)
944
        req = urllib.request.Request("http://www.roadrunner.net/")
945
        c.add_cookie_header(req)
946 947
        self.assertTrue(req.has_header("Cookie"))
        self.assertTrue(req.has_header("Cookie2"))
948 949 950 951

        c.clear()
        pol.set_blocked_domains([".acme.com"])
        c.extract_cookies(res, req)
952
        self.assertEqual(len(c), 1)
953 954

        # set a cookie with blocked domain...
955
        req = urllib.request.Request("http://www.acme.com/")
956 957 958
        res = FakeResponse(headers, "http://www.acme.com/")
        cookies = c.make_cookies(res, req)
        c.set_cookie(cookies[0])
959
        self.assertEqual(len(c), 2)
960 961
        # ... and check is doesn't get returned
        c.add_cookie_header(req)
962
        self.assertFalse(req.has_header("Cookie"))
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979

    def test_secure(self):
        for ns in True, False:
            for whitespace in " ", "":
                c = CookieJar()
                if ns:
                    pol = DefaultCookiePolicy(rfc2965=False)
                    int = interact_netscape
                    vs = ""
                else:
                    pol = DefaultCookiePolicy(rfc2965=True)
                    int = interact_2965
                    vs = "; Version=1"
                c.set_policy(pol)
                url = "http://www.acme.com/"
                int(c, url, "foo1=bar%s%s" % (vs, whitespace))
                int(c, url, "foo2=bar%s; secure%s" %  (vs, whitespace))
980 981
                self.assertFalse(
                    c._cookies["www.acme.com"]["/"]["foo1"].secure,
982
                    "non-secure cookie registered secure")
983
                self.assertTrue(
984 985 986 987 988 989 990
                    c._cookies["www.acme.com"]["/"]["foo2"].secure,
                    "secure cookie registered non-secure")

    def test_quote_cookie_value(self):
        c = CookieJar(policy=DefaultCookiePolicy(rfc2965=True))
        interact_2965(c, "http://www.acme.com/", r'foo=\b"a"r; Version=1')
        h = interact_2965(c, "http://www.acme.com/")
991
        self.assertEqual(h, r'$Version=1; foo=\\b\"a\"r')
992 993 994 995 996 997

    def test_missing_final_slash(self):
        # Missing slash from request URL's abs_path should be assumed present.
        url = "http://www.acme.com"
        c = CookieJar(DefaultCookiePolicy(rfc2965=True))
        interact_2965(c, url, "foo=bar; Version=1")
998
        req = urllib.request.Request(url)
999
        self.assertEqual(len(c), 1)
1000
        c.add_cookie_header(req)
1001
        self.assertTrue(req.has_header("Cookie"))
1002 1003 1004 1005 1006 1007 1008 1009

    def test_domain_mirror(self):
        pol = DefaultCookiePolicy(rfc2965=True)

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        interact_2965(c, url, "spam=eggs; Version=1")
        h = interact_2965(c, url)
1010
        self.assertNotIn("Domain", h,
1011 1012 1013 1014 1015 1016
                     "absent domain returned with domain present")

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        interact_2965(c, url, 'spam=eggs; Version=1; Domain=.bar.com')
        h = interact_2965(c, url)
1017
        self.assertIn('$Domain=".bar.com"', h, "domain not returned")
1018 1019 1020 1021 1022 1023

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        # note missing initial dot in Domain
        interact_2965(c, url, 'spam=eggs; Version=1; Domain=bar.com')
        h = interact_2965(c, url)
1024
        self.assertIn('$Domain="bar.com"', h, "domain not returned")
1025 1026 1027 1028 1029 1030 1031 1032

    def test_path_mirror(self):
        pol = DefaultCookiePolicy(rfc2965=True)

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        interact_2965(c, url, "spam=eggs; Version=1")
        h = interact_2965(c, url)
1033
        self.assertNotIn("Path", h, "absent path returned with path present")
1034 1035 1036 1037 1038

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        interact_2965(c, url, 'spam=eggs; Version=1; Path=/')
        h = interact_2965(c, url)
1039
        self.assertIn('$Path="/"', h, "path not returned")
1040 1041 1042 1043 1044 1045 1046 1047

    def test_port_mirror(self):
        pol = DefaultCookiePolicy(rfc2965=True)

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        interact_2965(c, url, "spam=eggs; Version=1")
        h = interact_2965(c, url)
1048
        self.assertNotIn("Port", h, "absent port returned with port present")
1049 1050 1051 1052 1053

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        interact_2965(c, url, "spam=eggs; Version=1; Port")
        h = interact_2965(c, url)
1054
        self.assertRegex(h, r"\$Port([^=]|$)",
1055
                         "port with no value not returned with no value")
1056 1057 1058 1059 1060

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        interact_2965(c, url, 'spam=eggs; Version=1; Port="80"')
        h = interact_2965(c, url)
1061 1062
        self.assertIn('$Port="80"', h,
                      "port with single value not returned with single value")
1063 1064 1065 1066 1067

        c = CookieJar(pol)
        url = "http://foo.bar.com/"
        interact_2965(c, url, 'spam=eggs; Version=1; Port="80,8080"')
        h = interact_2965(c, url)
1068 1069 1070
        self.assertIn('$Port="80,8080"', h,
                      "port with multiple values not returned with multiple "
                      "values")
1071 1072 1073 1074 1075 1076 1077 1078

    def test_no_return_comment(self):
        c = CookieJar(DefaultCookiePolicy(rfc2965=True))
        url = "http://foo.bar.com/"
        interact_2965(c, url, 'spam=eggs; Version=1; '
                      'Comment="does anybody read these?"; '
                      'CommentURL="http://foo.bar.net/comment.html"')
        h = interact_2965(c, url)
1079
        self.assertNotIn("Comment", h,
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
            "Comment or CommentURL cookie-attributes returned to server")

    def test_Cookie_iterator(self):
        cs = CookieJar(DefaultCookiePolicy(rfc2965=True))
        # add some random cookies
        interact_2965(cs, "http://blah.spam.org/", 'foo=eggs; Version=1; '
                      'Comment="does anybody read these?"; '
                      'CommentURL="http://foo.bar.net/comment.html"')
        interact_netscape(cs, "http://www.acme.com/blah/", "spam=bar; secure")
        interact_2965(cs, "http://www.acme.com/blah/",
                      "foo=bar; secure; Version=1")
        interact_2965(cs, "http://www.acme.com/blah/",
                      "foo=bar; path=/; Version=1")
        interact_2965(cs, "http://www.sol.no",
                      r'bang=wallop; version=1; domain=".sol.no"; '
                      r'port="90,100, 80,8080"; '
                      r'max-age=100; Comment = "Just kidding! (\"|\\\\) "')

        versions = [1, 1, 1, 0, 1]
        names = ["bang", "foo", "foo", "spam", "foo"]
        domains = [".sol.no", "blah.spam.org", "www.acme.com",
                   "www.acme.com", "www.acme.com"]
        paths = ["/", "/", "/", "/blah", "/blah/"]

        for i in range(4):
            i = 0
            for c in cs:
1107
                self.assertIsInstance(c, Cookie)
1108 1109 1110 1111
                self.assertEqual(c.version, versions[i])
                self.assertEqual(c.name, names[i])
                self.assertEqual(c.domain, domains[i])
                self.assertEqual(c.path, paths[i])
1112 1113 1114 1115
                i = i + 1

    def test_parse_ns_headers(self):
        # missing domain value (invalid cookie)
1116
        self.assertEqual(
1117 1118 1119 1120 1121
            parse_ns_headers(["foo=bar; path=/; domain"]),
            [[("foo", "bar"),
              ("path", "/"), ("domain", None), ("version", "0")]]
            )
        # invalid expires value
1122
        self.assertEqual(
1123 1124 1125 1126
            parse_ns_headers(["foo=bar; expires=Foo Bar 12 33:22:11 2000"]),
            [[("foo", "bar"), ("expires", None), ("version", "0")]]
            )
        # missing cookie value (valid cookie)
1127
        self.assertEqual(
1128 1129 1130
            parse_ns_headers(["foo"]),
            [[("foo", None), ("version", "0")]]
            )
1131 1132 1133 1134 1135 1136 1137
        # missing cookie values for parsed attributes
        self.assertEqual(
            parse_ns_headers(['foo=bar; expires']),
            [[('foo', 'bar'), ('expires', None), ('version', '0')]])
        self.assertEqual(
            parse_ns_headers(['foo=bar; version']),
            [[('foo', 'bar'), ('version', None)]])
1138
        # shouldn't add version if header is empty
1139
        self.assertEqual(parse_ns_headers([""]), [])
1140 1141 1142 1143 1144

    def test_bad_cookie_header(self):

        def cookiejar_from_cookie_headers(headers):
            c = CookieJar()
1145
            req = urllib.request.Request("http://www.example.com/")
1146 1147 1148 1149
            r = FakeResponse(headers, "http://www.example.com/")
            c.extract_cookies(r, req)
            return c

1150 1151
        future = time2netscape(time.time()+3600)

1152 1153 1154 1155 1156 1157 1158 1159
        # none of these bad headers should cause an exception to be raised
        for headers in [
            ["Set-Cookie: "],  # actually, nothing wrong with this
            ["Set-Cookie2: "],  # ditto
            # missing domain value
            ["Set-Cookie2: a=foo; path=/; Version=1; domain"],
            # bad max-age
            ["Set-Cookie: b=foo; max-age=oops"],
1160 1161
            # bad version
            ["Set-Cookie: b=foo; version=spam"],
1162
            ["Set-Cookie:; Expires=%s" % future],
1163 1164 1165
            ]:
            c = cookiejar_from_cookie_headers(headers)
            # these bad cookies shouldn't be set
1166
            self.assertEqual(len(c), 0)
1167 1168 1169 1170 1171

        # cookie with invalid expires is treated as session cookie
        headers = ["Set-Cookie: c=foo; expires=Foo Bar 12 33:22:11 2000"]
        c = cookiejar_from_cookie_headers(headers)
        cookie = c._cookies["www.example.com"]["/"]["c"]
1172
        self.assertIsNone(cookie.expires)
1173 1174


1175
class LWPCookieTests(unittest.TestCase):
1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
    # Tests taken from libwww-perl, with a few modifications and additions.

    def test_netscape_example_1(self):
        #-------------------------------------------------------------------
        # First we check that it works for the original example at
        # http://www.netscape.com/newsref/std/cookie_spec.html

        # Client requests a document, and receives in the response:
        #
        #       Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT
        #
        # When client requests a URL in path "/" on this server, it sends:
        #
        #       Cookie: CUSTOMER=WILE_E_COYOTE
        #
        # Client requests a document, and receives in the response:
        #
        #       Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/
        #
        # When client requests a URL in path "/" on this server, it sends:
        #
        #       Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001
        #
        # Client receives:
        #
        #       Set-Cookie: SHIPPING=FEDEX; path=/fo
        #
        # When client requests a URL in path "/" on this server, it sends:
        #
        #       Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001
        #
        # When client requests a URL in path "/foo" on this server, it sends:
        #
        #       Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX
        #
        # The last Cookie is buggy, because both specifications say that the
        # most specific cookie must be sent first.  SHIPPING=FEDEX is the
        # most specific and should thus be first.

        year_plus_one = time.localtime()[0] + 1

        headers = []

        c = CookieJar(DefaultCookiePolicy(rfc2965 = True))

1221
        #req = urllib.request.Request("http://1.1.1.1/",
1222
        #              headers={"Host": "www.acme.com:80"})
1223
        req = urllib.request.Request("http://www.acme.com:80/",
1224 1225 1226 1227 1228 1229 1230 1231
                      headers={"Host": "www.acme.com:80"})

        headers.append(
            "Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/ ; "
            "expires=Wednesday, 09-Nov-%d 23:12:40 GMT" % year_plus_one)
        res = FakeResponse(headers, "http://www.acme.com/")
        c.extract_cookies(res, req)

1232
        req = urllib.request.Request("http://www.acme.com/")
1233 1234 1235 1236 1237 1238 1239 1240 1241
        c.add_cookie_header(req)

        self.assertEqual(req.get_header("Cookie"), "CUSTOMER=WILE_E_COYOTE")
        self.assertEqual(req.get_header("Cookie2"), '$Version="1"')

        headers.append("Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/")
        res = FakeResponse(headers, "http://www.acme.com/")
        c.extract_cookies(res, req)

1242
        req = urllib.request.Request("http://www.acme.com/foo/bar")
1243 1244 1245
        c.add_cookie_header(req)

        h = req.get_header("Cookie")
1246 1247
        self.assertIn("PART_NUMBER=ROCKET_LAUNCHER_0001", h)
        self.assertIn("CUSTOMER=WILE_E_COYOTE", h)
1248 1249 1250 1251 1252

        headers.append('Set-Cookie: SHIPPING=FEDEX; path=/foo')
        res = FakeResponse(headers, "http://www.acme.com")
        c.extract_cookies(res, req)

1253
        req = urllib.request.Request("http://www.acme.com/")
1254 1255 1256
        c.add_cookie_header(req)

        h = req.get_header("Cookie")
1257 1258 1259
        self.assertIn("PART_NUMBER=ROCKET_LAUNCHER_0001", h)
        self.assertIn("CUSTOMER=WILE_E_COYOTE", h)
        self.assertNotIn("SHIPPING=FEDEX", h)
1260

1261
        req = urllib.request.Request("http://www.acme.com/foo/")
1262 1263 1264
        c.add_cookie_header(req)

        h = req.get_header("Cookie")
1265 1266 1267
        self.assertIn("PART_NUMBER=ROCKET_LAUNCHER_0001", h)
        self.assertIn("CUSTOMER=WILE_E_COYOTE", h)
        self.assertTrue(h.startswith("SHIPPING=FEDEX;"))
1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295

    def test_netscape_example_2(self):
        # Second Example transaction sequence:
        #
        # Assume all mappings from above have been cleared.
        #
        # Client receives:
        #
        #       Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/
        #
        # When client requests a URL in path "/" on this server, it sends:
        #
        #       Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001
        #
        # Client receives:
        #
        #       Set-Cookie: PART_NUMBER=RIDING_ROCKET_0023; path=/ammo
        #
        # When client requests a URL in path "/ammo" on this server, it sends:
        #
        #       Cookie: PART_NUMBER=RIDING_ROCKET_0023; PART_NUMBER=ROCKET_LAUNCHER_0001
        #
        #       NOTE: There are two name/value pairs named "PART_NUMBER" due to
        #       the inheritance of the "/" mapping in addition to the "/ammo" mapping.

        c = CookieJar()
        headers = []

1296
        req = urllib.request.Request("http://www.acme.com/")
1297 1298 1299 1300 1301
        headers.append("Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/")
        res = FakeResponse(headers, "http://www.acme.com/")

        c.extract_cookies(res, req)

1302
        req = urllib.request.Request("http://www.acme.com/")
1303 1304
        c.add_cookie_header(req)

1305 1306
        self.assertEqual(req.get_header("Cookie"),
                         "PART_NUMBER=ROCKET_LAUNCHER_0001")
1307 1308 1309 1310 1311 1312

        headers.append(
            "Set-Cookie: PART_NUMBER=RIDING_ROCKET_0023; path=/ammo")
        res = FakeResponse(headers, "http://www.acme.com/")
        c.extract_cookies(res, req)

1313
        req = urllib.request.Request("http://www.acme.com/ammo")
1314 1315
        c.add_cookie_header(req)

1316 1317 1318
        self.assertRegex(req.get_header("Cookie"),
                         r"PART_NUMBER=RIDING_ROCKET_0023;\s*"
                          "PART_NUMBER=ROCKET_LAUNCHER_0001")
1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350

    def test_ietf_example_1(self):
        #-------------------------------------------------------------------
        # Then we test with the examples from draft-ietf-http-state-man-mec-03.txt
        #
        # 5.  EXAMPLES

        c = CookieJar(DefaultCookiePolicy(rfc2965=True))

        #
        # 5.1  Example 1
        #
        # Most detail of request and response headers has been omitted.  Assume
        # the user agent has no stored cookies.
        #
        #   1.  User Agent -> Server
        #
        #       POST /acme/login HTTP/1.1
        #       [form data]
        #
        #       User identifies self via a form.
        #
        #   2.  Server -> User Agent
        #
        #       HTTP/1.1 200 OK
        #       Set-Cookie2: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
        #
        #       Cookie reflects user's identity.

        cookie = interact_2965(
            c, 'http://www.acme.com/acme/login',
            'Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"')
1351
        self.assertFalse(cookie)
1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372

        #
        #   3.  User Agent -> Server
        #
        #       POST /acme/pickitem HTTP/1.1
        #       Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"
        #       [form data]
        #
        #       User selects an item for ``shopping basket.''
        #
        #   4.  Server -> User Agent
        #
        #       HTTP/1.1 200 OK
        #       Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1";
        #               Path="/acme"
        #
        #       Shopping basket contains an item.

        cookie = interact_2965(c, 'http://www.acme.com/acme/pickitem',
                               'Part_Number="Rocket_Launcher_0001"; '
                               'Version="1"; Path="/acme"');
1373 1374
        self.assertRegex(cookie,
            r'^\$Version="?1"?; Customer="?WILE_E_COYOTE"?; \$Path="/acme"$')
1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396

        #
        #   5.  User Agent -> Server
        #
        #       POST /acme/shipping HTTP/1.1
        #       Cookie: $Version="1";
        #               Customer="WILE_E_COYOTE"; $Path="/acme";
        #               Part_Number="Rocket_Launcher_0001"; $Path="/acme"
        #       [form data]
        #
        #       User selects shipping method from form.
        #
        #   6.  Server -> User Agent
        #
        #       HTTP/1.1 200 OK
        #       Set-Cookie2: Shipping="FedEx"; Version="1"; Path="/acme"
        #
        #       New cookie reflects shipping method.

        cookie = interact_2965(c, "http://www.acme.com/acme/shipping",
                               'Shipping="FedEx"; Version="1"; Path="/acme"')

1397 1398
        self.assertRegex(cookie, r'^\$Version="?1"?;')
        self.assertRegex(cookie, r'Part_Number="?Rocket_Launcher_0001"?;'
1399
                                 r'\s*\$Path="\/acme"')
1400
        self.assertRegex(cookie, r'Customer="?WILE_E_COYOTE"?;'
1401
                                 r'\s*\$Path="\/acme"')
1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421

        #
        #   7.  User Agent -> Server
        #
        #       POST /acme/process HTTP/1.1
        #       Cookie: $Version="1";
        #               Customer="WILE_E_COYOTE"; $Path="/acme";
        #               Part_Number="Rocket_Launcher_0001"; $Path="/acme";
        #               Shipping="FedEx"; $Path="/acme"
        #       [form data]
        #
        #       User chooses to process order.
        #
        #   8.  Server -> User Agent
        #
        #       HTTP/1.1 200 OK
        #
        #       Transaction is complete.

        cookie = interact_2965(c, "http://www.acme.com/acme/process")
1422 1423
        self.assertRegex(cookie, r'Shipping="?FedEx"?;\s*\$Path="\/acme"')
        self.assertIn("WILE_E_COYOTE", cookie)
1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469

        #
        # The user agent makes a series of requests on the origin server, after
        # each of which it receives a new cookie.  All the cookies have the same
        # Path attribute and (default) domain.  Because the request URLs all have
        # /acme as a prefix, and that matches the Path attribute, each request
        # contains all the cookies received so far.

    def test_ietf_example_2(self):
        # 5.2  Example 2
        #
        # This example illustrates the effect of the Path attribute.  All detail
        # of request and response headers has been omitted.  Assume the user agent
        # has no stored cookies.

        c = CookieJar(DefaultCookiePolicy(rfc2965=True))

        # Imagine the user agent has received, in response to earlier requests,
        # the response headers
        #
        # Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1";
        #         Path="/acme"
        #
        # and
        #
        # Set-Cookie2: Part_Number="Riding_Rocket_0023"; Version="1";
        #         Path="/acme/ammo"

        interact_2965(
            c, "http://www.acme.com/acme/ammo/specific",
            'Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"',
            'Part_Number="Riding_Rocket_0023"; Version="1"; Path="/acme/ammo"')

        # A subsequent request by the user agent to the (same) server for URLs of
        # the form /acme/ammo/...  would include the following request header:
        #
        # Cookie: $Version="1";
        #         Part_Number="Riding_Rocket_0023"; $Path="/acme/ammo";
        #         Part_Number="Rocket_Launcher_0001"; $Path="/acme"
        #
        # Note that the NAME=VALUE pair for the cookie with the more specific Path
        # attribute, /acme/ammo, comes before the one with the less specific Path
        # attribute, /acme.  Further note that the same cookie name appears more
        # than once.

        cookie = interact_2965(c, "http://www.acme.com/acme/ammo/...")
1470
        self.assertRegex(cookie, r"Riding_Rocket_0023.*Rocket_Launcher_0001")
1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481

        # A subsequent request by the user agent to the (same) server for a URL of
        # the form /acme/parts/ would include the following request header:
        #
        # Cookie: $Version="1"; Part_Number="Rocket_Launcher_0001"; $Path="/acme"
        #
        # Here, the second cookie's Path attribute /acme/ammo is not a prefix of
        # the request URL, /acme/parts/, so the cookie does not get forwarded to
        # the server.

        cookie = interact_2965(c, "http://www.acme.com/acme/parts/")
1482 1483
        self.assertIn("Rocket_Launcher_0001", cookie)
        self.assertNotIn("Riding_Rocket_0023", cookie)
1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495

    def test_rejection(self):
        # Test rejection of Set-Cookie2 responses based on domain, path, port.
        pol = DefaultCookiePolicy(rfc2965=True)

        c = LWPCookieJar(policy=pol)

        max_age = "max-age=3600"

        # illegal domain (no embedded dots)
        cookie = interact_2965(c, "http://www.acme.com",
                               'foo=bar; domain=".com"; version=1')
1496
        self.assertFalse(c)
1497 1498 1499 1500

        # legal domain
        cookie = interact_2965(c, "http://www.acme.com",
                               'ping=pong; domain="acme.com"; version=1')
1501
        self.assertEqual(len(c), 1)
1502 1503 1504 1505

        # illegal domain (host prefix "www.a" contains a dot)
        cookie = interact_2965(c, "http://www.a.acme.com",
                               'whiz=bang; domain="acme.com"; version=1')
1506
        self.assertEqual(len(c), 1)
1507 1508 1509 1510

        # legal domain
        cookie = interact_2965(c, "http://www.a.acme.com",
                               'wow=flutter; domain=".a.acme.com"; version=1')
1511
        self.assertEqual(len(c), 2)
1512 1513 1514 1515

        # can't partially match an IP-address
        cookie = interact_2965(c, "http://125.125.125.125",
                               'zzzz=ping; domain="125.125.125"; version=1')
1516
        self.assertEqual(len(c), 2)
1517 1518 1519 1520 1521

        # illegal path (must be prefix of request path)
        cookie = interact_2965(c, "http://www.sol.no",
                               'blah=rhubarb; domain=".sol.no"; path="/foo"; '
                               'version=1')
1522
        self.assertEqual(len(c), 2)
1523 1524 1525 1526 1527

        # legal path
        cookie = interact_2965(c, "http://www.sol.no/foo/bar",
                               'bing=bong; domain=".sol.no"; path="/foo"; '
                               'version=1')
1528
        self.assertEqual(len(c), 3)
1529 1530 1531 1532 1533

        # illegal port (request-port not in list)
        cookie = interact_2965(c, "http://www.sol.no",
                               'whiz=ffft; domain=".sol.no"; port="90,100"; '
                               'version=1')
1534
        self.assertEqual(len(c), 3)
1535 1536 1537 1538 1539 1540 1541

        # legal port
        cookie = interact_2965(
            c, "http://www.sol.no",
            r'bang=wallop; version=1; domain=".sol.no"; '
            r'port="90,100, 80,8080"; '
            r'max-age=100; Comment = "Just kidding! (\"|\\\\) "')
1542
        self.assertEqual(len(c), 4)
1543 1544 1545 1546 1547

        # port attribute without any value (current port)
        cookie = interact_2965(c, "http://www.sol.no",
                               'foo9=bar; version=1; domain=".sol.no"; port; '
                               'max-age=100;')
1548
        self.assertEqual(len(c), 5)
1549 1550 1551 1552 1553 1554 1555 1556 1557 1558

        # encoded path
        # LWP has this test, but unescaping allowed path characters seems
        # like a bad idea, so I think this should fail:
##         cookie = interact_2965(c, "http://www.sol.no/foo/",
##                           r'foo8=bar; version=1; path="/%66oo"')
        # but this is OK, because '<' is not an allowed HTTP URL path
        # character:
        cookie = interact_2965(c, "http://www.sol.no/<oo/",
                               r'foo8=bar; version=1; path="/%3coo"')
1559
        self.assertEqual(len(c), 6)
1560 1561

        # save and restore
1562
        filename = test.support.TESTFN
1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573

        try:
            c.save(filename, ignore_discard=True)
            old = repr(c)

            c = LWPCookieJar(policy=pol)
            c.load(filename, ignore_discard=True)
        finally:
            try: os.unlink(filename)
            except OSError: pass

1574
        self.assertEqual(old, repr(c))
1575 1576 1577 1578 1579

    def test_url_encoding(self):
        # Try some URL encodings of the PATHs.
        # (the behaviour here has changed from libwww-perl)
        c = CookieJar(DefaultCookiePolicy(rfc2965=True))
1580 1581
        interact_2965(c, "http://www.acme.com/foo%2f%25/"
                         "%3c%3c%0Anew%C3%A5/%C3%A5",
1582 1583 1584
                      "foo  =   bar; version    =   1")

        cookie = interact_2965(
1585
            c, "http://www.acme.com/foo%2f%25/<<%0anew\345/\346\370\345",
1586 1587
            'bar=baz; path="/foo/"; version=1');
        version_re = re.compile(r'^\$version=\"?1\"?', re.I)
1588
        self.assertIn("foo=bar", cookie)
1589
        self.assertRegex(cookie, version_re)
1590 1591

        cookie = interact_2965(
1592
            c, "http://www.acme.com/foo/%25/<<%0anew\345/\346\370\345")
1593
        self.assertFalse(cookie)
1594 1595

        # unicode URL doesn't raise exception
1596
        cookie = interact_2965(c, "http://www.acme.com/\xfc")
1597 1598 1599 1600 1601

    def test_mozilla(self):
        # Save / load Mozilla/Netscape cookie file format.
        year_plus_one = time.localtime()[0] + 1

1602
        filename = test.support.TESTFN
1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631

        c = MozillaCookieJar(filename,
                             policy=DefaultCookiePolicy(rfc2965=True))
        interact_2965(c, "http://www.acme.com/",
                      "foo1=bar; max-age=100; Version=1")
        interact_2965(c, "http://www.acme.com/",
                      'foo2=bar; port="80"; max-age=100; Discard; Version=1')
        interact_2965(c, "http://www.acme.com/", "foo3=bar; secure; Version=1")

        expires = "expires=09-Nov-%d 23:12:40 GMT" % (year_plus_one,)
        interact_netscape(c, "http://www.foo.com/",
                          "fooa=bar; %s" % expires)
        interact_netscape(c, "http://www.foo.com/",
                          "foob=bar; Domain=.foo.com; %s" % expires)
        interact_netscape(c, "http://www.foo.com/",
                          "fooc=bar; Domain=www.foo.com; %s" % expires)

        def save_and_restore(cj, ignore_discard):
            try:
                cj.save(ignore_discard=ignore_discard)
                new_c = MozillaCookieJar(filename,
                                         DefaultCookiePolicy(rfc2965=True))
                new_c.load(ignore_discard=ignore_discard)
            finally:
                try: os.unlink(filename)
                except OSError: pass
            return new_c

        new_c = save_and_restore(c, True)
1632
        self.assertEqual(len(new_c), 6)  # none discarded
1633
        self.assertIn("name='foo1', value='bar'", repr(new_c))
1634 1635

        new_c = save_and_restore(c, False)
1636
        self.assertEqual(len(new_c), 4)  # 2 of them discarded on save
1637
        self.assertIn("name='foo1', value='bar'", repr(new_c))
1638 1639 1640 1641 1642

    def test_netscape_misc(self):
        # Some additional Netscape cookies tests.
        c = CookieJar()
        headers = []
1643
        req = urllib.request.Request("http://foo.bar.acme.com/foo")
1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656

        # Netscape allows a host part that contains dots
        headers.append("Set-Cookie: Customer=WILE_E_COYOTE; domain=.acme.com")
        res = FakeResponse(headers, "http://www.acme.com/foo")
        c.extract_cookies(res, req)

        # and that the domain is the same as the host without adding a leading
        # dot to the domain.  Should not quote even if strange chars are used
        # in the cookie value.
        headers.append("Set-Cookie: PART_NUMBER=3,4; domain=foo.bar.acme.com")
        res = FakeResponse(headers, "http://www.acme.com/foo")
        c.extract_cookies(res, req)

1657
        req = urllib.request.Request("http://foo.bar.acme.com/foo")
1658
        c.add_cookie_header(req)
1659 1660
        self.assertIn("PART_NUMBER=3,4", req.get_header("Cookie"))
        self.assertIn("Customer=WILE_E_COYOTE",req.get_header("Cookie"))
1661 1662 1663 1664 1665 1666 1667 1668

    def test_intranet_domains_2965(self):
        # Test handling of local intranet hostnames without a dot.
        c = CookieJar(DefaultCookiePolicy(rfc2965=True))
        interact_2965(c, "http://example/",
                      "foo1=bar; PORT; Discard; Version=1;")
        cookie = interact_2965(c, "http://example/",
                               'foo2=bar; domain=".local"; Version=1')
1669
        self.assertIn("foo1=bar", cookie)
1670 1671 1672

        interact_2965(c, "http://example/", 'foo3=bar; Version=1')
        cookie = interact_2965(c, "http://example/")
1673
        self.assertIn("foo2=bar", cookie)
1674
        self.assertEqual(len(c), 3)
1675 1676 1677 1678 1679 1680

    def test_intranet_domains_ns(self):
        c = CookieJar(DefaultCookiePolicy(rfc2965 = False))
        interact_netscape(c, "http://example/", "foo1=bar")
        cookie = interact_netscape(c, "http://example/",
                                   'foo2=bar; domain=.local')
1681
        self.assertEqual(len(c), 2)
1682
        self.assertIn("foo1=bar", cookie)
1683 1684

        cookie = interact_netscape(c, "http://example/")
1685
        self.assertIn("foo2=bar", cookie)
1686
        self.assertEqual(len(c), 2)
1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698

    def test_empty_path(self):
        # Test for empty path
        # Broken web-server ORION/1.3.38 returns to the client response like
        #
        #       Set-Cookie: JSESSIONID=ABCDERANDOM123; Path=
        #
        # ie. with Path set to nothing.
        # In this case, extract_cookies() must set cookie to / (root)
        c = CookieJar(DefaultCookiePolicy(rfc2965 = True))
        headers = []

1699
        req = urllib.request.Request("http://www.ants.com/")
1700 1701 1702 1703
        headers.append("Set-Cookie: JSESSIONID=ABCDERANDOM123; Path=")
        res = FakeResponse(headers, "http://www.ants.com/")
        c.extract_cookies(res, req)

1704
        req = urllib.request.Request("http://www.ants.com/")
1705 1706
        c.add_cookie_header(req)

1707 1708 1709
        self.assertEqual(req.get_header("Cookie"),
                         "JSESSIONID=ABCDERANDOM123")
        self.assertEqual(req.get_header("Cookie2"), '$Version="1"')
1710 1711

        # missing path in the request URI
1712
        req = urllib.request.Request("http://www.ants.com:8080")
1713 1714
        c.add_cookie_header(req)

1715 1716 1717
        self.assertEqual(req.get_header("Cookie"),
                         "JSESSIONID=ABCDERANDOM123")
        self.assertEqual(req.get_header("Cookie2"), '$Version="1"')
1718 1719 1720 1721 1722 1723 1724

    def test_session_cookies(self):
        year_plus_one = time.localtime()[0] + 1

        # Check session cookies are deleted properly by
        # CookieJar.clear_session_cookies method

1725
        req = urllib.request.Request('http://www.perlmeister.com/scripts')
1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753
        headers = []
        headers.append("Set-Cookie: s1=session;Path=/scripts")
        headers.append("Set-Cookie: p1=perm; Domain=.perlmeister.com;"
                       "Path=/;expires=Fri, 02-Feb-%d 23:24:20 GMT" %
                       year_plus_one)
        headers.append("Set-Cookie: p2=perm;Path=/;expires=Fri, "
                       "02-Feb-%d 23:24:20 GMT" % year_plus_one)
        headers.append("Set-Cookie: s2=session;Path=/scripts;"
                       "Domain=.perlmeister.com")
        headers.append('Set-Cookie2: s3=session;Version=1;Discard;Path="/"')
        res = FakeResponse(headers, 'http://www.perlmeister.com/scripts')

        c = CookieJar()
        c.extract_cookies(res, req)
        # How many session/permanent cookies do we have?
        counter = {"session_after": 0,
                   "perm_after": 0,
                   "session_before": 0,
                   "perm_before": 0}
        for cookie in c:
            key = "%s_before" % cookie.value
            counter[key] = counter[key] + 1
        c.clear_session_cookies()
        # How many now?
        for cookie in c:
            key = "%s_after" % cookie.value
            counter[key] = counter[key] + 1

1754
            # a permanent cookie got lost accidentally
1755
        self.assertEqual(counter["perm_after"], counter["perm_before"])
1756
            # a session cookie hasn't been cleared
1757
        self.assertEqual(counter["session_after"], 0)
1758
            # we didn't have session cookies in the first place
1759
        self.assertNotEqual(counter["session_before"], 0)
1760 1761 1762


def test_main(verbose=None):
1763
    test.support.run_unittest(
1764 1765 1766
        DateTimeTests,
        HeaderTests,
        CookieTests,
1767
        FileCookieJarTests,
1768 1769 1770 1771 1772
        LWPCookieTests,
        )

if __name__ == "__main__":
    test_main(verbose=True)