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