django.contrib.messages.storage.cookie: 76 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/django/contrib/messages/storage/cookie.py

Stats: 0 executed, 71 missed, 5 excluded, 75 ignored

  1. from django.conf import settings
  2. from django.contrib.messages.storage.base import BaseStorage, Message
  3. from django.http import SimpleCookie
  4. from django.utils import simplejson as json
  5. from django.utils.crypto import salted_hmac, constant_time_compare
  6. class MessageEncoder(json.JSONEncoder):
  7. """
  8. Compactly serializes instances of the ``Message`` class as JSON.
  9. """
  10. message_key = '__json_message'
  11. def default(self, obj):
  12. if isinstance(obj, Message):
  13. message = [self.message_key, obj.level, obj.message]
  14. if obj.extra_tags:
  15. message.append(obj.extra_tags)
  16. return message
  17. return super(MessageEncoder, self).default(obj)
  18. class MessageDecoder(json.JSONDecoder):
  19. """
  20. Decodes JSON that includes serialized ``Message`` instances.
  21. """
  22. def process_messages(self, obj):
  23. if isinstance(obj, list) and obj:
  24. if obj[0] == MessageEncoder.message_key:
  25. return Message(*obj[1:])
  26. return [self.process_messages(item) for item in obj]
  27. if isinstance(obj, dict):
  28. return dict([(key, self.process_messages(value))
  29. for key, value in obj.iteritems()])
  30. return obj
  31. def decode(self, s, **kwargs):
  32. decoded = super(MessageDecoder, self).decode(s, **kwargs)
  33. return self.process_messages(decoded)
  34. class CookieStorage(BaseStorage):
  35. """
  36. Stores messages in a cookie.
  37. """
  38. cookie_name = 'messages'
  39. # We should be able to store 4K in a cookie, but Internet Explorer
  40. # imposes 4K as the *total* limit for a domain. To allow other
  41. # cookies, we go for 3/4 of 4K.
  42. max_cookie_size = 3072
  43. not_finished = '__messagesnotfinished__'
  44. def _get(self, *args, **kwargs):
  45. """
  46. Retrieves a list of messages from the messages cookie. If the
  47. not_finished sentinel value is found at the end of the message list,
  48. remove it and return a result indicating that not all messages were
  49. retrieved by this storage.
  50. """
  51. data = self.request.COOKIES.get(self.cookie_name)
  52. messages = self._decode(data)
  53. all_retrieved = not (messages and messages[-1] == self.not_finished)
  54. if messages and not all_retrieved:
  55. # remove the sentinel value
  56. messages.pop()
  57. return messages, all_retrieved
  58. def _update_cookie(self, encoded_data, response):
  59. """
  60. Either sets the cookie with the encoded data if there is any data to
  61. store, or deletes the cookie.
  62. """
  63. if encoded_data:
  64. response.set_cookie(self.cookie_name, encoded_data,
  65. domain=settings.SESSION_COOKIE_DOMAIN)
  66. else:
  67. response.delete_cookie(self.cookie_name,
  68. domain=settings.SESSION_COOKIE_DOMAIN)
  69. def _store(self, messages, response, remove_oldest=True, *args, **kwargs):
  70. """
  71. Stores the messages to a cookie, returning a list of any messages which
  72. could not be stored.
  73. If the encoded data is larger than ``max_cookie_size``, removes
  74. messages until the data fits (these are the messages which are
  75. returned), and add the not_finished sentinel value to indicate as much.
  76. """
  77. unstored_messages = []
  78. encoded_data = self._encode(messages)
  79. if self.max_cookie_size:
  80. # data is going to be stored eventually by SimpleCookie, which
  81. # adds it's own overhead, which we must account for.
  82. cookie = SimpleCookie() # create outside the loop
  83. def stored_length(val):
  84. return len(cookie.value_encode(val)[1])
  85. while encoded_data and stored_length(encoded_data) > self.max_cookie_size:
  86. if remove_oldest:
  87. unstored_messages.append(messages.pop(0))
  88. else:
  89. unstored_messages.insert(0, messages.pop())
  90. encoded_data = self._encode(messages + [self.not_finished],
  91. encode_empty=unstored_messages)
  92. self._update_cookie(encoded_data, response)
  93. return unstored_messages
  94. def _hash(self, value):
  95. """
  96. Creates an HMAC/SHA1 hash based on the value and the project setting's
  97. SECRET_KEY, modified to make it unique for the present purpose.
  98. """
  99. key_salt = 'django.contrib.messages'
  100. return salted_hmac(key_salt, value).hexdigest()
  101. def _encode(self, messages, encode_empty=False):
  102. """
  103. Returns an encoded version of the messages list which can be stored as
  104. plain text.
  105. Since the data will be retrieved from the client-side, the encoded data
  106. also contains a hash to ensure that the data was not tampered with.
  107. """
  108. if messages or encode_empty:
  109. encoder = MessageEncoder(separators=(',', ':'))
  110. value = encoder.encode(messages)
  111. return '%s$%s' % (self._hash(value), value)
  112. def _decode(self, data):
  113. """
  114. Safely decodes a encoded text stream back into a list of messages.
  115. If the encoded text stream contained an invalid hash or was in an
  116. invalid format, ``None`` is returned.
  117. """
  118. if not data:
  119. return None
  120. bits = data.split('$', 1)
  121. if len(bits) == 2:
  122. hash, value = bits
  123. if constant_time_compare(hash, self._hash(value)):
  124. try:
  125. # If we get here (and the JSON decode works), everything is
  126. # good. In any other case, drop back and return None.
  127. return json.loads(value, cls=MessageDecoder)
  128. except ValueError:
  129. pass
  130. # Mark the data as used (so it gets removed) since something was wrong
  131. # with the data.
  132. self.used = True
  133. return None