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