"""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)