Skip to content

Commit 8ab75a2

Browse files
Ryan P KilbyCarlton Gibson
authored andcommitted
Add 'STRICT_JSON' API setting.
STRICT_JSON controls the renderer & parser behavior on whether or not to accept non-standard float values (NaN, Infinity).
1 parent d740bae commit 8ab75a2

File tree

6 files changed

+48
-5
lines changed

6 files changed

+48
-5
lines changed

rest_framework/parsers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from rest_framework import renderers
2424
from rest_framework.exceptions import ParseError
25+
from rest_framework.settings import api_settings
2526
from rest_framework.utils import json
2627

2728

@@ -53,6 +54,7 @@ class JSONParser(BaseParser):
5354
"""
5455
media_type = 'application/json'
5556
renderer_class = renderers.JSONRenderer
57+
strict = api_settings.STRICT_JSON
5658

5759
def parse(self, stream, media_type=None, parser_context=None):
5860
"""
@@ -63,7 +65,8 @@ def parse(self, stream, media_type=None, parser_context=None):
6365

6466
try:
6567
decoded_stream = codecs.getreader(encoding)(stream)
66-
return json.load(decoded_stream)
68+
parse_constant = json.strict_constant if self.strict else None
69+
return json.load(decoded_stream, parse_constant=parse_constant)
6770
except ValueError as exc:
6871
raise ParseError('JSON parse error - %s' % six.text_type(exc))
6972

rest_framework/renderers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class JSONRenderer(BaseRenderer):
6161
encoder_class = encoders.JSONEncoder
6262
ensure_ascii = not api_settings.UNICODE_JSON
6363
compact = api_settings.COMPACT_JSON
64+
strict = api_settings.STRICT_JSON
6465

6566
# We don't set a charset because JSON is a binary encoding,
6667
# that can be encoded as utf-8, utf-16 or utf-32.
@@ -101,7 +102,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
101102
ret = json.dumps(
102103
data, cls=self.encoder_class,
103104
indent=indent, ensure_ascii=self.ensure_ascii,
104-
separators=separators
105+
allow_nan=not self.strict, separators=separators
105106
)
106107

107108
# On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,

rest_framework/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
# Encoding
111111
'UNICODE_JSON': True,
112112
'COMPACT_JSON': True,
113+
'STRICT_JSON': True,
113114
'COERCE_DECIMAL_TO_STRING': True,
114115
'UPLOADED_FILES_USE_URL': True,
115116

tests/test_parsers.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
# -*- coding: utf-8 -*-
2-
32
from __future__ import unicode_literals
43

4+
import math
5+
56
import pytest
67
from django import forms
78
from django.core.files.uploadhandler import (
89
MemoryFileUploadHandler, TemporaryFileUploadHandler
910
)
1011
from django.http.request import RawPostDataException
1112
from django.test import TestCase
12-
from django.utils.six.moves import StringIO
13+
from django.utils.six import BytesIO, StringIO
1314

1415
from rest_framework.exceptions import ParseError
1516
from rest_framework.parsers import (
@@ -42,7 +43,6 @@ class TestFileUploadParser(TestCase):
4243
def setUp(self):
4344
class MockRequest(object):
4445
pass
45-
from io import BytesIO
4646
self.stream = BytesIO(
4747
"Test text file".encode('utf-8')
4848
)
@@ -129,6 +129,24 @@ def __replace_content_disposition(self, disposition):
129129
self.parser_context['request'].META['HTTP_CONTENT_DISPOSITION'] = disposition
130130

131131

132+
class TestJSONParser(TestCase):
133+
def bytes(self, value):
134+
return BytesIO(value.encode('utf-8'))
135+
136+
def test_float_strictness(self):
137+
parser = JSONParser()
138+
139+
# Default to strict
140+
for value in ['Infinity', '-Infinity', 'NaN']:
141+
with pytest.raises(ParseError):
142+
parser.parse(self.bytes(value))
143+
144+
parser.strict = False
145+
assert parser.parse(self.bytes('Infinity')) == float('inf')
146+
assert parser.parse(self.bytes('-Infinity')) == float('-inf')
147+
assert math.isnan(parser.parse(self.bytes('NaN')))
148+
149+
132150
class TestPOSTAccessed(TestCase):
133151
def setUp(self):
134152
self.factory = APIRequestFactory()

tests/test_renderers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,19 @@ def __getitem__(self, key):
358358
with self.assertRaises(TypeError):
359359
JSONRenderer().render(x)
360360

361+
def test_float_strictness(self):
362+
renderer = JSONRenderer()
363+
364+
# Default to strict
365+
for value in [float('inf'), float('-inf'), float('nan')]:
366+
with pytest.raises(ValueError):
367+
renderer.render(value)
368+
369+
renderer.strict = False
370+
assert renderer.render(float('inf')) == b'Infinity'
371+
assert renderer.render(float('-inf')) == b'-Infinity'
372+
assert renderer.render(float('nan')) == b'NaN'
373+
361374
def test_without_content_type_args(self):
362375
"""
363376
Test basic JSON rendering.

tests/test_utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,10 @@ def test_loads(self):
198198

199199
with self.assertRaises(ValueError):
200200
json.loads("NaN")
201+
202+
203+
@override_settings(STRICT_JSON=False)
204+
class NonStrictJsonFloatTests(JsonFloatTests):
205+
"""
206+
'STRICT_JSON = False' should not somehow affect internal json behavior
207+
"""

0 commit comments

Comments
 (0)