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