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