1# Simple test suite for http/cookies.py
2
3import copy
4from test.support import run_unittest, run_doctest
5import unittest
6from http import cookies
7import pickle
8
9
10class CookieTests(unittest.TestCase):
11
12    def test_basic(self):
13        cases = [
14            {'data': 'chips=ahoy; vienna=finger',
15             'dict': {'chips':'ahoy', 'vienna':'finger'},
16             'repr': "<SimpleCookie: chips='ahoy' vienna='finger'>",
17             'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'},
18
19            {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"',
20             'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'},
21             'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=\\n;'>''',
22             'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"'},
23
24            # Check illegal cookies that have an '=' char in an unquoted value
25            {'data': 'keebler=E=mc2',
26             'dict': {'keebler' : 'E=mc2'},
27             'repr': "<SimpleCookie: keebler='E=mc2'>",
28             'output': 'Set-Cookie: keebler=E=mc2'},
29
30            # Cookies with ':' character in their name. Though not mentioned in
31            # RFC, servers / browsers allow it.
32
33             {'data': 'key:term=value:term',
34             'dict': {'key:term' : 'value:term'},
35             'repr': "<SimpleCookie: key:term='value:term'>",
36             'output': 'Set-Cookie: key:term=value:term'},
37
38            # issue22931 - Adding '[' and ']' as valid characters in cookie
39            # values as defined in RFC 6265
40            {
41                'data': 'a=b; c=[; d=r; f=h',
42                'dict': {'a':'b', 'c':'[', 'd':'r', 'f':'h'},
43                'repr': "<SimpleCookie: a='b' c='[' d='r' f='h'>",
44                'output': '\n'.join((
45                    'Set-Cookie: a=b',
46                    'Set-Cookie: c=[',
47                    'Set-Cookie: d=r',
48                    'Set-Cookie: f=h'
49                ))
50            }
51        ]
52
53        for case in cases:
54            C = cookies.SimpleCookie()
55            C.load(case['data'])
56            self.assertEqual(repr(C), case['repr'])
57            self.assertEqual(C.output(sep='\n'), case['output'])
58            for k, v in sorted(case['dict'].items()):
59                self.assertEqual(C[k].value, v)
60
61    def test_load(self):
62        C = cookies.SimpleCookie()
63        C.load('Customer="WILE_E_COYOTE"; Version=1; Path=/acme')
64
65        self.assertEqual(C['Customer'].value, 'WILE_E_COYOTE')
66        self.assertEqual(C['Customer']['version'], '1')
67        self.assertEqual(C['Customer']['path'], '/acme')
68
69        self.assertEqual(C.output(['path']),
70            'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
71        self.assertEqual(C.js_output(), r"""
72        <script type="text/javascript">
73        <!-- begin hiding
74        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
75        // end hiding -->
76        </script>
77        """)
78        self.assertEqual(C.js_output(['path']), r"""
79        <script type="text/javascript">
80        <!-- begin hiding
81        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
82        // end hiding -->
83        </script>
84        """)
85
86    def test_extended_encode(self):
87        # Issue 9824: some browsers don't follow the standard; we now
88        # encode , and ; to keep them from tripping up.
89        C = cookies.SimpleCookie()
90        C['val'] = "some,funky;stuff"
91        self.assertEqual(C.output(['val']),
92            'Set-Cookie: val="some\\054funky\\073stuff"')
93
94    def test_special_attrs(self):
95        # 'expires'
96        C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
97        C['Customer']['expires'] = 0
98        # can't test exact output, it always depends on current date/time
99        self.assertTrue(C.output().endswith('GMT'))
100
101        # loading 'expires'
102        C = cookies.SimpleCookie()
103        C.load('Customer="W"; expires=Wed, 01 Jan 2010 00:00:00 GMT')
104        self.assertEqual(C['Customer']['expires'],
105                         'Wed, 01 Jan 2010 00:00:00 GMT')
106        C = cookies.SimpleCookie()
107        C.load('Customer="W"; expires=Wed, 01 Jan 98 00:00:00 GMT')
108        self.assertEqual(C['Customer']['expires'],
109                         'Wed, 01 Jan 98 00:00:00 GMT')
110
111        # 'max-age'
112        C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
113        C['Customer']['max-age'] = 10
114        self.assertEqual(C.output(),
115                         'Set-Cookie: Customer="WILE_E_COYOTE"; Max-Age=10')
116
117    def test_set_secure_httponly_attrs(self):
118        C = cookies.SimpleCookie('Customer="WILE_E_COYOTE"')
119        C['Customer']['secure'] = True
120        C['Customer']['httponly'] = True
121        self.assertEqual(C.output(),
122            'Set-Cookie: Customer="WILE_E_COYOTE"; HttpOnly; Secure')
123
124    def test_secure_httponly_false_if_not_present(self):
125        C = cookies.SimpleCookie()
126        C.load('eggs=scrambled; Path=/bacon')
127        self.assertFalse(C['eggs']['httponly'])
128        self.assertFalse(C['eggs']['secure'])
129
130    def test_secure_httponly_true_if_present(self):
131        # Issue 16611
132        C = cookies.SimpleCookie()
133        C.load('eggs=scrambled; httponly; secure; Path=/bacon')
134        self.assertTrue(C['eggs']['httponly'])
135        self.assertTrue(C['eggs']['secure'])
136
137    def test_secure_httponly_true_if_have_value(self):
138        # This isn't really valid, but demonstrates what the current code
139        # is expected to do in this case.
140        C = cookies.SimpleCookie()
141        C.load('eggs=scrambled; httponly=foo; secure=bar; Path=/bacon')
142        self.assertTrue(C['eggs']['httponly'])
143        self.assertTrue(C['eggs']['secure'])
144        # Here is what it actually does; don't depend on this behavior.  These
145        # checks are testing backward compatibility for issue 16611.
146        self.assertEqual(C['eggs']['httponly'], 'foo')
147        self.assertEqual(C['eggs']['secure'], 'bar')
148
149    def test_extra_spaces(self):
150        C = cookies.SimpleCookie()
151        C.load('eggs  =  scrambled  ;  secure  ;  path  =  bar   ; foo=foo   ')
152        self.assertEqual(C.output(),
153            'Set-Cookie: eggs=scrambled; Path=bar; Secure\r\nSet-Cookie: foo=foo')
154
155    def test_quoted_meta(self):
156        # Try cookie with quoted meta-data
157        C = cookies.SimpleCookie()
158        C.load('Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"')
159        self.assertEqual(C['Customer'].value, 'WILE_E_COYOTE')
160        self.assertEqual(C['Customer']['version'], '1')
161        self.assertEqual(C['Customer']['path'], '/acme')
162
163        self.assertEqual(C.output(['path']),
164                         'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
165        self.assertEqual(C.js_output(), r"""
166        <script type="text/javascript">
167        <!-- begin hiding
168        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
169        // end hiding -->
170        </script>
171        """)
172        self.assertEqual(C.js_output(['path']), r"""
173        <script type="text/javascript">
174        <!-- begin hiding
175        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
176        // end hiding -->
177        </script>
178        """)
179
180    def test_invalid_cookies(self):
181        # Accepting these could be a security issue
182        C = cookies.SimpleCookie()
183        for s in (']foo=x', '[foo=x', 'blah]foo=x', 'blah[foo=x',
184                  'Set-Cookie: foo=bar', 'Set-Cookie: foo',
185                  'foo=bar; baz', 'baz; foo=bar',
186                  'secure;foo=bar', 'Version=1;foo=bar'):
187            C.load(s)
188            self.assertEqual(dict(C), {})
189            self.assertEqual(C.output(), '')
190
191    def test_pickle(self):
192        rawdata = 'Customer="WILE_E_COYOTE"; Path=/acme; Version=1'
193        expected_output = 'Set-Cookie: %s' % rawdata
194
195        C = cookies.SimpleCookie()
196        C.load(rawdata)
197        self.assertEqual(C.output(), expected_output)
198
199        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
200            with self.subTest(proto=proto):
201                C1 = pickle.loads(pickle.dumps(C, protocol=proto))
202                self.assertEqual(C1.output(), expected_output)
203
204    def test_illegal_chars(self):
205        rawdata = "a=b; c,d=e"
206        C = cookies.SimpleCookie()
207        with self.assertRaises(cookies.CookieError):
208            C.load(rawdata)
209
210    def test_comment_quoting(self):
211        c = cookies.SimpleCookie()
212        c['foo'] = '\N{COPYRIGHT SIGN}'
213        self.assertEqual(str(c['foo']), 'Set-Cookie: foo="\\251"')
214        c['foo']['comment'] = 'comment \N{COPYRIGHT SIGN}'
215        self.assertEqual(
216            str(c['foo']),
217            'Set-Cookie: foo="\\251"; Comment="comment \\251"'
218        )
219
220
221class MorselTests(unittest.TestCase):
222    """Tests for the Morsel object."""
223
224    def test_defaults(self):
225        morsel = cookies.Morsel()
226        self.assertIsNone(morsel.key)
227        self.assertIsNone(morsel.value)
228        self.assertIsNone(morsel.coded_value)
229        self.assertEqual(morsel.keys(), cookies.Morsel._reserved.keys())
230        for key, val in morsel.items():
231            self.assertEqual(val, '', key)
232
233    def test_reserved_keys(self):
234        M = cookies.Morsel()
235        # tests valid and invalid reserved keys for Morsels
236        for i in M._reserved:
237            # Test that all valid keys are reported as reserved and set them
238            self.assertTrue(M.isReservedKey(i))
239            M[i] = '%s_value' % i
240        for i in M._reserved:
241            # Test that valid key values come out fine
242            self.assertEqual(M[i], '%s_value' % i)
243        for i in "the holy hand grenade".split():
244            # Test that invalid keys raise CookieError
245            self.assertRaises(cookies.CookieError,
246                              M.__setitem__, i, '%s_value' % i)
247
248    def test_setter(self):
249        M = cookies.Morsel()
250        # tests the .set method to set keys and their values
251        for i in M._reserved:
252            # Makes sure that all reserved keys can't be set this way
253            self.assertRaises(cookies.CookieError,
254                              M.set, i, '%s_value' % i, '%s_value' % i)
255        for i in "thou cast _the- !holy! ^hand| +*grenade~".split():
256            # Try typical use case. Setting decent values.
257            # Check output and js_output.
258            M['path'] = '/foo' # Try a reserved key as well
259            M.set(i, "%s_val" % i, "%s_coded_val" % i)
260            self.assertEqual(M.key, i)
261            self.assertEqual(M.value, "%s_val" % i)
262            self.assertEqual(M.coded_value, "%s_coded_val" % i)
263            self.assertEqual(
264                M.output(),
265                "Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i))
266            expected_js_output = """
267        <script type="text/javascript">
268        <!-- begin hiding
269        document.cookie = "%s=%s; Path=/foo";
270        // end hiding -->
271        </script>
272        """ % (i, "%s_coded_val" % i)
273            self.assertEqual(M.js_output(), expected_js_output)
274        for i in ["foo bar", "foo@bar"]:
275            # Try some illegal characters
276            self.assertRaises(cookies.CookieError,
277                              M.set, i, '%s_value' % i, '%s_value' % i)
278
279    def test_set_properties(self):
280        morsel = cookies.Morsel()
281        with self.assertRaises(AttributeError):
282            morsel.key = ''
283        with self.assertRaises(AttributeError):
284            morsel.value = ''
285        with self.assertRaises(AttributeError):
286            morsel.coded_value = ''
287
288    def test_eq(self):
289        base_case = ('key', 'value', '"value"')
290        attribs = {
291            'path': '/',
292            'comment': 'foo',
293            'domain': 'example.com',
294            'version': 2,
295        }
296        morsel_a = cookies.Morsel()
297        morsel_a.update(attribs)
298        morsel_a.set(*base_case)
299        morsel_b = cookies.Morsel()
300        morsel_b.update(attribs)
301        morsel_b.set(*base_case)
302        self.assertTrue(morsel_a == morsel_b)
303        self.assertFalse(morsel_a != morsel_b)
304        cases = (
305            ('key', 'value', 'mismatch'),
306            ('key', 'mismatch', '"value"'),
307            ('mismatch', 'value', '"value"'),
308        )
309        for case_b in cases:
310            with self.subTest(case_b):
311                morsel_b = cookies.Morsel()
312                morsel_b.update(attribs)
313                morsel_b.set(*case_b)
314                self.assertFalse(morsel_a == morsel_b)
315                self.assertTrue(morsel_a != morsel_b)
316
317        morsel_b = cookies.Morsel()
318        morsel_b.update(attribs)
319        morsel_b.set(*base_case)
320        morsel_b['comment'] = 'bar'
321        self.assertFalse(morsel_a == morsel_b)
322        self.assertTrue(morsel_a != morsel_b)
323
324        # test mismatched types
325        self.assertFalse(cookies.Morsel() == 1)
326        self.assertTrue(cookies.Morsel() != 1)
327        self.assertFalse(cookies.Morsel() == '')
328        self.assertTrue(cookies.Morsel() != '')
329        items = list(cookies.Morsel().items())
330        self.assertFalse(cookies.Morsel() == items)
331        self.assertTrue(cookies.Morsel() != items)
332
333        # morsel/dict
334        morsel = cookies.Morsel()
335        morsel.set(*base_case)
336        morsel.update(attribs)
337        self.assertTrue(morsel == dict(morsel))
338        self.assertFalse(morsel != dict(morsel))
339
340    def test_copy(self):
341        morsel_a = cookies.Morsel()
342        morsel_a.set('foo', 'bar', 'baz')
343        morsel_a.update({
344            'version': 2,
345            'comment': 'foo',
346        })
347        morsel_b = morsel_a.copy()
348        self.assertIsInstance(morsel_b, cookies.Morsel)
349        self.assertIsNot(morsel_a, morsel_b)
350        self.assertEqual(morsel_a, morsel_b)
351
352        morsel_b = copy.copy(morsel_a)
353        self.assertIsInstance(morsel_b, cookies.Morsel)
354        self.assertIsNot(morsel_a, morsel_b)
355        self.assertEqual(morsel_a, morsel_b)
356
357    def test_setitem(self):
358        morsel = cookies.Morsel()
359        morsel['expires'] = 0
360        self.assertEqual(morsel['expires'], 0)
361        morsel['Version'] = 2
362        self.assertEqual(morsel['version'], 2)
363        morsel['DOMAIN'] = 'example.com'
364        self.assertEqual(morsel['domain'], 'example.com')
365
366        with self.assertRaises(cookies.CookieError):
367            morsel['invalid'] = 'value'
368        self.assertNotIn('invalid', morsel)
369
370    def test_setdefault(self):
371        morsel = cookies.Morsel()
372        morsel.update({
373            'domain': 'example.com',
374            'version': 2,
375        })
376        # this shouldn't override the default value
377        self.assertEqual(morsel.setdefault('expires', 'value'), '')
378        self.assertEqual(morsel['expires'], '')
379        self.assertEqual(morsel.setdefault('Version', 1), 2)
380        self.assertEqual(morsel['version'], 2)
381        self.assertEqual(morsel.setdefault('DOMAIN', 'value'), 'example.com')
382        self.assertEqual(morsel['domain'], 'example.com')
383
384        with self.assertRaises(cookies.CookieError):
385            morsel.setdefault('invalid', 'value')
386        self.assertNotIn('invalid', morsel)
387
388    def test_update(self):
389        attribs = {'expires': 1, 'Version': 2, 'DOMAIN': 'example.com'}
390        # test dict update
391        morsel = cookies.Morsel()
392        morsel.update(attribs)
393        self.assertEqual(morsel['expires'], 1)
394        self.assertEqual(morsel['version'], 2)
395        self.assertEqual(morsel['domain'], 'example.com')
396        # test iterable update
397        morsel = cookies.Morsel()
398        morsel.update(list(attribs.items()))
399        self.assertEqual(morsel['expires'], 1)
400        self.assertEqual(morsel['version'], 2)
401        self.assertEqual(morsel['domain'], 'example.com')
402        # test iterator update
403        morsel = cookies.Morsel()
404        morsel.update((k, v) for k, v in attribs.items())
405        self.assertEqual(morsel['expires'], 1)
406        self.assertEqual(morsel['version'], 2)
407        self.assertEqual(morsel['domain'], 'example.com')
408
409        with self.assertRaises(cookies.CookieError):
410            morsel.update({'invalid': 'value'})
411        self.assertNotIn('invalid', morsel)
412        self.assertRaises(TypeError, morsel.update)
413        self.assertRaises(TypeError, morsel.update, 0)
414
415    def test_pickle(self):
416        morsel_a = cookies.Morsel()
417        morsel_a.set('foo', 'bar', 'baz')
418        morsel_a.update({
419            'version': 2,
420            'comment': 'foo',
421        })
422        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
423            with self.subTest(proto=proto):
424                morsel_b = pickle.loads(pickle.dumps(morsel_a, proto))
425                self.assertIsInstance(morsel_b, cookies.Morsel)
426                self.assertEqual(morsel_b, morsel_a)
427                self.assertEqual(str(morsel_b), str(morsel_a))
428
429    def test_repr(self):
430        morsel = cookies.Morsel()
431        self.assertEqual(repr(morsel), '<Morsel: None=None>')
432        self.assertEqual(str(morsel), 'Set-Cookie: None=None')
433        morsel.set('key', 'val', 'coded_val')
434        self.assertEqual(repr(morsel), '<Morsel: key=coded_val>')
435        self.assertEqual(str(morsel), 'Set-Cookie: key=coded_val')
436        morsel.update({
437            'path': '/',
438            'comment': 'foo',
439            'domain': 'example.com',
440            'max-age': 0,
441            'secure': 0,
442            'version': 1,
443        })
444        self.assertEqual(repr(morsel),
445                '<Morsel: key=coded_val; Comment=foo; Domain=example.com; '
446                'Max-Age=0; Path=/; Version=1>')
447        self.assertEqual(str(morsel),
448                'Set-Cookie: key=coded_val; Comment=foo; Domain=example.com; '
449                'Max-Age=0; Path=/; Version=1')
450        morsel['secure'] = True
451        morsel['httponly'] = 1
452        self.assertEqual(repr(morsel),
453                '<Morsel: key=coded_val; Comment=foo; Domain=example.com; '
454                'HttpOnly; Max-Age=0; Path=/; Secure; Version=1>')
455        self.assertEqual(str(morsel),
456                'Set-Cookie: key=coded_val; Comment=foo; Domain=example.com; '
457                'HttpOnly; Max-Age=0; Path=/; Secure; Version=1')
458
459        morsel = cookies.Morsel()
460        morsel.set('key', 'val', 'coded_val')
461        morsel['expires'] = 0
462        self.assertRegex(repr(morsel),
463                r'<Morsel: key=coded_val; '
464                r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+>')
465        self.assertRegex(str(morsel),
466                r'Set-Cookie: key=coded_val; '
467                r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+')
468
469def test_main():
470    run_unittest(CookieTests, MorselTests)
471    run_doctest(cookies)
472
473if __name__ == '__main__':
474    test_main()
475