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