From fd0351aef246f5d36e641209429e2ec093d325f8 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Fri, 23 Sep 2011 16:26:10 +0200 Subject: [PATCH] serializer: Fail if dictionary uses invalid keys MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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: Michael Hanselmann Reviewed-by: René Nussbaumer --- lib/serializer.py | 22 +++++++++++++++++++++- test/ganeti.serializer_unittest.py | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/serializer.py b/lib/serializer.py index 5b80675..090ae00 100644 --- a/lib/serializer.py +++ b/lib/serializer.py @@ -29,14 +29,18 @@ backend (currently json). # C0103: Invalid name, since pylint doesn't see that Dump points to a # function and not a constant +_OLD_SIMPLEJSON = False + try: import json except ImportError: # The "json" module was only added in Python 2.6. Earlier versions must use # the separate "simplejson" module. import simplejson as json + _OLD_SIMPLEJSON = True import re +import logging from ganeti import errors from ganeti import utils @@ -47,7 +51,23 @@ _JSON_INDENT = 2 _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. @rtype: (callable, callable) diff --git a/test/ganeti.serializer_unittest.py b/test/ganeti.serializer_unittest.py index 9d8d656..3e68efa 100755 --- a/test/ganeti.serializer_unittest.py +++ b/test/ganeti.serializer_unittest.py @@ -23,9 +23,11 @@ import unittest +import warnings from ganeti import serializer from ganeti import errors +from ganeti import compat import testutils @@ -107,5 +109,24 @@ class TestSerializer(testutils.GanetiTestCase): 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__': testutils.GanetiTestProgram() -- 1.7.10.4