diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 5ebaeca2e..7a256276a 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -22,6 +22,7 @@ from django.utils.six.moves.urllib import parse as urlparse from rest_framework import renderers from rest_framework.exceptions import ParseError +from rest_framework.settings import api_settings from rest_framework.utils import json @@ -53,6 +54,7 @@ class JSONParser(BaseParser): """ media_type = 'application/json' renderer_class = renderers.JSONRenderer + strict = api_settings.STRICT_JSON def parse(self, stream, media_type=None, parser_context=None): """ @@ -63,7 +65,8 @@ class JSONParser(BaseParser): try: decoded_stream = codecs.getreader(encoding)(stream) - return json.load(decoded_stream) + parse_constant = json.strict_constant if self.strict else None + return json.load(decoded_stream, parse_constant=parse_constant) except ValueError as exc: raise ParseError('JSON parse error - %s' % six.text_type(exc)) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index a87eb1889..90f516b35 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -61,6 +61,7 @@ class JSONRenderer(BaseRenderer): encoder_class = encoders.JSONEncoder ensure_ascii = not api_settings.UNICODE_JSON compact = api_settings.COMPACT_JSON + strict = api_settings.STRICT_JSON # We don't set a charset because JSON is a binary encoding, # that can be encoded as utf-8, utf-16 or utf-32. @@ -101,7 +102,7 @@ class JSONRenderer(BaseRenderer): ret = json.dumps( data, cls=self.encoder_class, indent=indent, ensure_ascii=self.ensure_ascii, - separators=separators + allow_nan=not self.strict, separators=separators ) # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 3f3c9110a..bc42c38a6 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -110,6 +110,7 @@ DEFAULTS = { # Encoding 'UNICODE_JSON': True, 'COMPACT_JSON': True, + 'STRICT_JSON': True, 'COERCE_DECIMAL_TO_STRING': True, 'UPLOADED_FILES_USE_URL': True, diff --git a/tests/test_parsers.py b/tests/test_parsers.py index 2499bfa3a..0a18aad1a 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- - from __future__ import unicode_literals +import math + import pytest from django import forms from django.core.files.uploadhandler import ( @@ -9,7 +10,7 @@ from django.core.files.uploadhandler import ( ) from django.http.request import RawPostDataException from django.test import TestCase -from django.utils.six.moves import StringIO +from django.utils.six import BytesIO, StringIO from rest_framework.exceptions import ParseError from rest_framework.parsers import ( @@ -42,7 +43,6 @@ class TestFileUploadParser(TestCase): def setUp(self): class MockRequest(object): pass - from io import BytesIO self.stream = BytesIO( "Test text file".encode('utf-8') ) @@ -129,6 +129,24 @@ class TestFileUploadParser(TestCase): self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition +class TestJSONParser(TestCase): + def bytes(self, value): + return BytesIO(value.encode('utf-8')) + + def test_float_strictness(self): + parser = JSONParser() + + # Default to strict + for value in ['Infinity', '-Infinity', 'NaN']: + with pytest.raises(ParseError): + parser.parse(self.bytes(value)) + + parser.strict = False + assert parser.parse(self.bytes('Infinity')) == float('inf') + assert parser.parse(self.bytes('-Infinity')) == float('-inf') + assert math.isnan(parser.parse(self.bytes('NaN'))) + + class TestPOSTAccessed(TestCase): def setUp(self): self.factory = APIRequestFactory() diff --git a/tests/test_renderers.py b/tests/test_renderers.py index ccc910c63..5f4aa51a3 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -358,6 +358,19 @@ class JSONRendererTests(TestCase): with self.assertRaises(TypeError): JSONRenderer().render(x) + def test_float_strictness(self): + renderer = JSONRenderer() + + # Default to strict + for value in [float('inf'), float('-inf'), float('nan')]: + with pytest.raises(ValueError): + renderer.render(value) + + renderer.strict = False + assert renderer.render(float('inf')) == b'Infinity' + assert renderer.render(float('-inf')) == b'-Infinity' + assert renderer.render(float('nan')) == b'NaN' + def test_without_content_type_args(self): """ Test basic JSON rendering. diff --git a/tests/test_utils.py b/tests/test_utils.py index 9619776d6..c5e6f26dc 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -198,3 +198,10 @@ class JsonFloatTests(TestCase): with self.assertRaises(ValueError): json.loads("NaN") + + +@override_settings(STRICT_JSON=False) +class NonStrictJsonFloatTests(JsonFloatTests): + """ + 'STRICT_JSON = False' should not somehow affect internal json behavior + """