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