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