test_cookie.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import json
  2. from django.contrib.messages import constants
  3. from django.contrib.messages.storage.base import Message
  4. from django.contrib.messages.storage.cookie import (
  5. CookieStorage, MessageDecoder, MessageEncoder,
  6. )
  7. from django.test import SimpleTestCase, override_settings
  8. from django.utils.safestring import SafeData, mark_safe
  9. from .base import BaseTests
  10. def set_cookie_data(storage, messages, invalid=False, encode_empty=False):
  11. """
  12. Sets ``request.COOKIES`` with the encoded data and removes the storage
  13. backend's loaded data cache.
  14. """
  15. encoded_data = storage._encode(messages, encode_empty=encode_empty)
  16. if invalid:
  17. # Truncate the first character so that the hash is invalid.
  18. encoded_data = encoded_data[1:]
  19. storage.request.COOKIES = {CookieStorage.cookie_name: encoded_data}
  20. if hasattr(storage, '_loaded_data'):
  21. del storage._loaded_data
  22. def stored_cookie_messages_count(storage, response):
  23. """
  24. Returns an integer containing the number of messages stored.
  25. """
  26. # Get a list of cookies, excluding ones with a max-age of 0 (because
  27. # they have been marked for deletion).
  28. cookie = response.cookies.get(storage.cookie_name)
  29. if not cookie or cookie['max-age'] == 0:
  30. return 0
  31. data = storage._decode(cookie.value)
  32. if not data:
  33. return 0
  34. if data[-1] == CookieStorage.not_finished:
  35. data.pop()
  36. return len(data)
  37. @override_settings(SESSION_COOKIE_DOMAIN='.example.com', SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True)
  38. class CookieTest(BaseTests, SimpleTestCase):
  39. storage_class = CookieStorage
  40. def stored_messages_count(self, storage, response):
  41. return stored_cookie_messages_count(storage, response)
  42. def test_get(self):
  43. storage = self.storage_class(self.get_request())
  44. # Set initial data.
  45. example_messages = ['test', 'me']
  46. set_cookie_data(storage, example_messages)
  47. # Test that the message actually contains what we expect.
  48. self.assertEqual(list(storage), example_messages)
  49. def test_cookie_setings(self):
  50. """
  51. Ensure that CookieStorage honors SESSION_COOKIE_DOMAIN, SESSION_COOKIE_SECURE and SESSION_COOKIE_HTTPONLY
  52. Refs #15618 and #20972.
  53. """
  54. # Test before the messages have been consumed
  55. storage = self.get_storage()
  56. response = self.get_response()
  57. storage.add(constants.INFO, 'test')
  58. storage.update(response)
  59. self.assertIn('test', response.cookies['messages'].value)
  60. self.assertEqual(response.cookies['messages']['domain'], '.example.com')
  61. self.assertEqual(response.cookies['messages']['expires'], '')
  62. self.assertIs(response.cookies['messages']['secure'], True)
  63. self.assertIs(response.cookies['messages']['httponly'], True)
  64. # Test deletion of the cookie (storing with an empty value) after the messages have been consumed
  65. storage = self.get_storage()
  66. response = self.get_response()
  67. storage.add(constants.INFO, 'test')
  68. for m in storage:
  69. pass # Iterate through the storage to simulate consumption of messages.
  70. storage.update(response)
  71. self.assertEqual(response.cookies['messages'].value, '')
  72. self.assertEqual(response.cookies['messages']['domain'], '.example.com')
  73. self.assertEqual(response.cookies['messages']['expires'], 'Thu, 01-Jan-1970 00:00:00 GMT')
  74. def test_get_bad_cookie(self):
  75. request = self.get_request()
  76. storage = self.storage_class(request)
  77. # Set initial (invalid) data.
  78. example_messages = ['test', 'me']
  79. set_cookie_data(storage, example_messages, invalid=True)
  80. # Test that the message actually contains what we expect.
  81. self.assertEqual(list(storage), [])
  82. def test_max_cookie_length(self):
  83. """
  84. Tests that, if the data exceeds what is allowed in a cookie, older
  85. messages are removed before saving (and returned by the ``update``
  86. method).
  87. """
  88. storage = self.get_storage()
  89. response = self.get_response()
  90. # When storing as a cookie, the cookie has constant overhead of approx
  91. # 54 chars, and each message has a constant overhead of about 37 chars
  92. # and a variable overhead of zero in the best case. We aim for a message
  93. # size which will fit 4 messages into the cookie, but not 5.
  94. # See also FallbackTest.test_session_fallback
  95. msg_size = int((CookieStorage.max_cookie_size - 54) / 4.5 - 37)
  96. for i in range(5):
  97. storage.add(constants.INFO, str(i) * msg_size)
  98. unstored_messages = storage.update(response)
  99. cookie_storing = self.stored_messages_count(storage, response)
  100. self.assertEqual(cookie_storing, 4)
  101. self.assertEqual(len(unstored_messages), 1)
  102. self.assertEqual(unstored_messages[0].message, '0' * msg_size)
  103. def test_json_encoder_decoder(self):
  104. """
  105. Tests that a complex nested data structure containing Message
  106. instances is properly encoded/decoded by the custom JSON
  107. encoder/decoder classes.
  108. """
  109. messages = [
  110. {
  111. 'message': Message(constants.INFO, 'Test message'),
  112. 'message_list': [
  113. Message(constants.INFO, 'message %s') for x in range(5)
  114. ] + [{'another-message': Message(constants.ERROR, 'error')}],
  115. },
  116. Message(constants.INFO, 'message %s'),
  117. ]
  118. encoder = MessageEncoder(separators=(',', ':'))
  119. value = encoder.encode(messages)
  120. decoded_messages = json.loads(value, cls=MessageDecoder)
  121. self.assertEqual(messages, decoded_messages)
  122. def test_safedata(self):
  123. """
  124. Tests that a message containing SafeData is keeping its safe status when
  125. retrieved from the message storage.
  126. """
  127. def encode_decode(data):
  128. message = Message(constants.DEBUG, data)
  129. encoded = storage._encode(message)
  130. decoded = storage._decode(encoded)
  131. return decoded.message
  132. storage = self.get_storage()
  133. self.assertIsInstance(
  134. encode_decode(mark_safe("<b>Hello Django!</b>")), SafeData)
  135. self.assertNotIsInstance(
  136. encode_decode("<b>Hello Django!</b>"), SafeData)
  137. def test_pre_1_5_message_format(self):
  138. """
  139. For ticket #22426. Tests whether messages that were set in the cookie
  140. before the addition of is_safedata are decoded correctly.
  141. """
  142. # Encode the messages using the current encoder.
  143. messages = [Message(constants.INFO, 'message %s') for x in range(5)]
  144. encoder = MessageEncoder(separators=(',', ':'))
  145. encoded_messages = encoder.encode(messages)
  146. # Remove the is_safedata flag from the messages in order to imitate
  147. # the behavior of before 1.5 (monkey patching).
  148. encoded_messages = json.loads(encoded_messages)
  149. for obj in encoded_messages:
  150. obj.pop(1)
  151. encoded_messages = json.dumps(encoded_messages, separators=(',', ':'))
  152. # Decode the messages in the old format (without is_safedata)
  153. decoded_messages = json.loads(encoded_messages, cls=MessageDecoder)
  154. self.assertEqual(messages, decoded_messages)