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