File size: 2,653 Bytes
ecad52f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | """Bounded UTF-8 JSON boundary, rejecting duplicate keys and non-finite values."""
import json
MAX_JSON_BYTES = 1_048_576
MAX_JSON_DEPTH = 32
def _pairs(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError("Duplicate JSON key")
result[key] = value
return result
def _constant(value):
raise ValueError("Non-finite JSON number")
def _float(value):
import math
parsed = float(value)
if not math.isfinite(parsed):
raise ValueError("Non-finite JSON number")
return parsed
def _integer(value):
# Cross-runtime bound, independent of Python's changing int conversion cap.
if len(value.lstrip("-")) > 128:
raise ValueError("JSON integer exceeds 128 digits")
return int(value)
def _check_text(text):
if not isinstance(text, str) or len(text) > MAX_JSON_BYTES:
raise ValueError("JSON input must be bounded UTF-8 text")
try:
if len(text.encode("utf-8", errors="strict")) > MAX_JSON_BYTES:
raise ValueError("JSON input too large")
except UnicodeEncodeError as exc:
raise ValueError("Invalid Unicode scalar value") from exc
# Limit nesting before asking a recursive JSON decoder to allocate objects.
depth = 0
quoted = escaped = False
for ch in text:
if quoted:
if escaped:
escaped = False
elif ch == "\\":
escaped = True
elif ch == '"':
quoted = False
elif ch == '"':
quoted = True
elif ch in "[{":
depth += 1
if depth > MAX_JSON_DEPTH:
raise ValueError("JSON nesting too deep")
elif ch in "]}":
depth -= 1
def strict_loads(text):
_check_text(text)
try:
value = json.loads(text, object_pairs_hook=_pairs, parse_constant=_constant, parse_float=_float, parse_int=_integer)
# Escaped surrogate pairs are decoded normally; lone surrogates fail.
stack = [value]
while stack:
item = stack.pop()
if isinstance(item, str):
item.encode("utf-8", errors="strict")
elif isinstance(item, dict):
stack.extend(item.keys())
stack.extend(item.values())
elif isinstance(item, list):
stack.extend(item)
return value
except (UnicodeError, RecursionError) as exc:
raise ValueError("Invalid JSON Unicode or nesting") from exc
def strict_dumps(value):
return json.dumps(value, ensure_ascii=True, sort_keys=True, indent=2, allow_nan=False)
|