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