diff options
-rw-r--r-- | CHANGES.txt | 7 | ||||
-rw-r--r-- | conf.py | 4 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | simplejson/__init__.py | 2 | ||||
-rw-r--r-- | simplejson/_speedups.c | 47 | ||||
-rw-r--r-- | simplejson/encoder.py | 9 | ||||
-rw-r--r-- | simplejson/tests/__init__.py | 1 | ||||
-rw-r--r-- | simplejson/tests/test_subclass.py | 37 |
8 files changed, 101 insertions, 8 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 76b7d78..ab95258 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,10 @@ +Version 3.7.0 released 2015-05-18 + +* simplejson no longer trusts custom str/repr methods for int, long, float + subclasses. These instances are now formatted as if they were exact + instances of those types. + https://github.com/simplejson/simplejson/issues/118 + Version 3.6.5 released 2014-10-24 * Importing bug fix for reference leak when an error occurs during @@ -42,9 +42,9 @@ copyright = '2014, Bob Ippolito' # other places throughout the built documents. # # The short X.Y version. -version = '3.6' +version = '3.7' # The full version, including alpha/beta/rc tags. -release = '3.6.5' +release = '3.7.0' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -11,7 +11,7 @@ from distutils.errors import CCompilerError, DistutilsExecError, \ DistutilsPlatformError IS_PYPY = hasattr(sys, 'pypy_translation_info') -VERSION = '3.6.5' +VERSION = '3.7.0' DESCRIPTION = "Simple, fast, extensible JSON encoder/decoder for Python" with open('README.rst', 'r') as f: diff --git a/simplejson/__init__.py b/simplejson/__init__.py index 8c0b698..0ba6ab0 100644 --- a/simplejson/__init__.py +++ b/simplejson/__init__.py @@ -98,7 +98,7 @@ Using simplejson.tool from the shell to validate and pretty-print:: Expecting property name: line 1 column 3 (char 2) """ from __future__ import absolute_import -__version__ = '3.6.5' +__version__ = '3.7.0' __all__ = [ 'dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', diff --git a/simplejson/_speedups.c b/simplejson/_speedups.c index 0b2d81c..65cf444 100644 --- a/simplejson/_speedups.c +++ b/simplejson/_speedups.c @@ -10,6 +10,7 @@ #define PyString_AS_STRING PyBytes_AS_STRING #define PyString_FromStringAndSize PyBytes_FromStringAndSize #define PyInt_Check(obj) 0 +#define PyInt_CheckExact(obj) 0 #define JSON_UNICHR Py_UCS4 #define JSON_InternFromString PyUnicode_InternFromString #define JSON_Intern_GET_SIZE PyUnicode_GET_SIZE @@ -660,7 +661,19 @@ encoder_stringify_key(PyEncoderObject *s, PyObject *key) return _encoded_const(key); } else if (PyInt_Check(key) || PyLong_Check(key)) { - return PyObject_Str(key); + if (!(PyInt_CheckExact(key) || PyLong_CheckExact(key))) { + /* See #118, do not trust custom str/repr */ + PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyLong_Type, key, NULL); + if (tmp == NULL) { + return NULL; + } + PyObject *res = PyObject_Str(tmp); + Py_DECREF(tmp); + return res; + } + else { + return PyObject_Str(key); + } } else if (s->use_decimal && PyObject_TypeCheck(key, (PyTypeObject *)s->Decimal)) { return PyObject_Str(key); @@ -2637,7 +2650,7 @@ encoder_init(PyObject *self, PyObject *args, PyObject *kwds) s->tuple_as_array = PyObject_IsTrue(tuple_as_array); if (PyInt_Check(int_as_string_bitcount) || PyLong_Check(int_as_string_bitcount)) { static const unsigned int long_long_bitsize = SIZEOF_LONG_LONG * 8; - int int_as_string_bitcount_val = PyLong_AsLong(int_as_string_bitcount); + int int_as_string_bitcount_val = (int)PyLong_AsLong(int_as_string_bitcount); if (int_as_string_bitcount_val > 0 && int_as_string_bitcount_val < long_long_bitsize) { s->max_long_size = PyLong_FromUnsignedLongLong(1ULL << int_as_string_bitcount_val); s->min_long_size = PyLong_FromLongLong(-1LL << int_as_string_bitcount_val); @@ -2800,7 +2813,19 @@ encoder_encode_float(PyEncoderObject *s, PyObject *obj) } } /* Use a better float format here? */ - return PyObject_Repr(obj); + if (PyFloat_CheckExact(obj)) { + return PyObject_Repr(obj); + } + else { + /* See #118, do not trust custom str/repr */ + PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyFloat_Type, obj, NULL); + if (tmp == NULL) { + return NULL; + } + PyObject *res = PyObject_Repr(tmp); + Py_DECREF(tmp); + return res; + } } static PyObject * @@ -2840,7 +2865,21 @@ encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ss rv = _steal_accumulate(rval, encoded); } else if (PyInt_Check(obj) || PyLong_Check(obj)) { - PyObject *encoded = PyObject_Str(obj); + PyObject *encoded; + if (PyInt_CheckExact(obj) || PyLong_CheckExact(obj)) { + encoded = PyObject_Str(obj); + } + else { + /* See #118, do not trust custom str/repr */ + PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyLong_Type, obj, NULL); + if (tmp == NULL) { + encoded = NULL; + } + else { + encoded = PyObject_Str(tmp); + Py_DECREF(tmp); + } + } if (encoded != NULL) { encoded = maybe_quote_bigint(s, encoded, obj); if (encoded == NULL) diff --git a/simplejson/encoder.py b/simplejson/encoder.py index db18244..7f0b037 100644 --- a/simplejson/encoder.py +++ b/simplejson/encoder.py @@ -311,6 +311,9 @@ class JSONEncoder(object): elif o == _neginf: text = '-Infinity' else: + if type(o) != float: + # See #118, do not trust custom str/repr + o = float(o) return _repr(o) if ignore_nan: @@ -412,6 +415,9 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, or _int_as_string_bitcount < 1 ) + if type(value) not in integer_types: + # See #118, do not trust custom str/repr + value = int(value) if ( skip_quoting or (-1 << _int_as_string_bitcount) @@ -501,6 +507,9 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, elif key is None: key = 'null' elif isinstance(key, integer_types): + if key not in integer_types: + # See #118, do not trust custom str/repr + key = int(key) key = str(key) elif _use_decimal and isinstance(key, Decimal): key = str(key) diff --git a/simplejson/tests/__init__.py b/simplejson/tests/__init__.py index c7551e8..8c1a4f1 100644 --- a/simplejson/tests/__init__.py +++ b/simplejson/tests/__init__.py @@ -62,6 +62,7 @@ def all_tests_suite(): 'simplejson.tests.test_namedtuple', 'simplejson.tests.test_tool', 'simplejson.tests.test_for_json', + 'simplejson.tests.test_subclass', ])) suite = get_suite() import simplejson diff --git a/simplejson/tests/test_subclass.py b/simplejson/tests/test_subclass.py new file mode 100644 index 0000000..2bae3b6 --- /dev/null +++ b/simplejson/tests/test_subclass.py @@ -0,0 +1,37 @@ +from unittest import TestCase +import simplejson as json + +from decimal import Decimal + +class AlternateInt(int): + def __repr__(self): + return 'invalid json' + __str__ = __repr__ + + +class AlternateFloat(float): + def __repr__(self): + return 'invalid json' + __str__ = __repr__ + + +# class AlternateDecimal(Decimal): +# def __repr__(self): +# return 'invalid json' + + +class TestSubclass(TestCase): + def test_int(self): + self.assertEqual(json.dumps(AlternateInt(1)), '1') + self.assertEqual(json.dumps(AlternateInt(-1)), '-1') + self.assertEqual(json.loads(json.dumps({AlternateInt(1): 1})), {'1': 1}) + + def test_float(self): + self.assertEqual(json.dumps(AlternateFloat(1.0)), '1.0') + self.assertEqual(json.dumps(AlternateFloat(-1.0)), '-1.0') + self.assertEqual(json.loads(json.dumps({AlternateFloat(1.0): 1})), {'1.0': 1}) + + # NOTE: Decimal subclasses are not supported as-is + # def test_decimal(self): + # self.assertEqual(json.dumps(AlternateDecimal('1.0')), '1.0') + # self.assertEqual(json.dumps(AlternateDecimal('-1.0')), '-1.0') |