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