Skip to content
Snippets Groups Projects
Commit fd0351ae authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

serializer: Fail if dictionary uses invalid keys


JSON only supports a very restricted set of types for dictionary keys,
among them strings, booleans and “null”. Integers and floats are
converted to strings. Since this can cause a lot of confusion in Python,
this check raises an exception if a caller tries to use such types.

Since the pre-Python 2.6 “simplejson” module doesn't support overriding
the function where the conversion takes place this check can only be
done for the newer “json” module.

Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarRené Nussbaumer <rn@google.com>
parent 9869e771
No related branches found
No related tags found
No related merge requests found
...@@ -29,14 +29,18 @@ backend (currently json). ...@@ -29,14 +29,18 @@ backend (currently json).
# C0103: Invalid name, since pylint doesn't see that Dump points to a # C0103: Invalid name, since pylint doesn't see that Dump points to a
# function and not a constant # function and not a constant
_OLD_SIMPLEJSON = False
try: try:
import json import json
except ImportError: except ImportError:
# The "json" module was only added in Python 2.6. Earlier versions must use # The "json" module was only added in Python 2.6. Earlier versions must use
# the separate "simplejson" module. # the separate "simplejson" module.
import simplejson as json import simplejson as json
_OLD_SIMPLEJSON = True
import re import re
import logging
from ganeti import errors from ganeti import errors
from ganeti import utils from ganeti import utils
...@@ -47,7 +51,23 @@ _JSON_INDENT = 2 ...@@ -47,7 +51,23 @@ _JSON_INDENT = 2
_RE_EOLSP = re.compile("[ \t]+$", re.MULTILINE) _RE_EOLSP = re.compile("[ \t]+$", re.MULTILINE)
def _GetJsonDumpers(_encoder_class=json.JSONEncoder): class _CustomJsonEncoder(json.JSONEncoder):
if __debug__ and not _OLD_SIMPLEJSON:
try:
_orig_fn = json.JSONEncoder._iterencode_dict
except AttributeError:
raise Exception("Can't override JSONEncoder's '_iterencode_dict'")
else:
def _iterencode_dict(self, data, *args, **kwargs):
for key in data.keys():
if not (key is None or isinstance(key, (basestring, bool))):
raise ValueError("Key '%s' is of disallowed type '%s'" %
(key, type(key)))
return self._orig_fn(data, *args, **kwargs)
def _GetJsonDumpers(_encoder_class=_CustomJsonEncoder):
"""Returns two JSON functions to serialize data. """Returns two JSON functions to serialize data.
@rtype: (callable, callable) @rtype: (callable, callable)
......
...@@ -23,9 +23,11 @@ ...@@ -23,9 +23,11 @@
import unittest import unittest
import warnings
from ganeti import serializer from ganeti import serializer
from ganeti import errors from ganeti import errors
from ganeti import compat
import testutils import testutils
...@@ -107,5 +109,24 @@ class TestSerializer(testutils.GanetiTestCase): ...@@ -107,5 +109,24 @@ class TestSerializer(testutils.GanetiTestCase):
serializer.DumpJson(tdata), "mykey") serializer.DumpJson(tdata), "mykey")
class TestInvalidDictionaryKey(unittest.TestCase):
def _Test(self, data):
if serializer._OLD_SIMPLEJSON:
# Using old "simplejson", can't really test
warnings.warn("This test requires Python 2.6 or above to function"
" correctly")
self.assertTrue(serializer.DumpJson(data))
else:
self.assertRaises(ValueError, serializer.DumpJson, data)
def test(self):
for value in [123, 1.1, -1, -9492.1123, -3234e-4]:
self._Test({value: ""})
def testAllowed(self):
for value in ["", "Hello World", None, True, False]:
self.assertTrue(serializer.DumpJson({value: ""}))
if __name__ == '__main__': if __name__ == '__main__':
testutils.GanetiTestProgram() testutils.GanetiTestProgram()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment