import { setPhpRuntimeEntry } from '../_helpers/_phpRuntimeState.ts' type JsonPrimitive = string | number | boolean | null type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue } export function json_decode(strJson: string): T | null { // discuss at: https://phpjs.org/functions/json_decode/ // parity verified: PHP 8.3 // original by: Public Domain (https://www.json.org/json2.js) // reimplemented by: Kevin van Zonneveld (https://kevin.vanzonneveld.net) // improved by: T.J. Leahy // improved by: Michael White // note 1: If node or the browser does not offer JSON.parse, // note 1: this function falls backslash // note 1: to its own implementation using eval, and hence should be considered unsafe // example 1: json_decode('[ 1 ]') // returns 1: [1] /* https://www.JSON.org/json2.js 2008-11-19 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See https://www.JSON.org/js.html */ const json = typeof JSON === 'object' && JSON !== null ? JSON : null if (typeof json === 'object' && json !== null) { const parse = json.parse if (typeof parse === 'function') { try { const parsed = parse.call(json, strJson) setPhpRuntimeEntry('last_error_json', 0) return parsed } catch (err) { if (!(err instanceof SyntaxError)) { throw new Error('Unexpected error type in json_decode()') } // usable by json_last_error() setPhpRuntimeEntry('last_error_json', 4) return null } } } const chars = [ '\u0000', '\u00ad', '\u0600-\u0604', '\u070f', '\u17b4', '\u17b5', '\u200c-\u200f', '\u2028-\u202f', '\u2060-\u206f', '\ufeff', '\ufff0-\uffff', ].join('') const cx = new RegExp('[' + chars + ']', 'g') let text = strJson // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. cx.lastIndex = 0 if (cx.test(text)) { text = text.replace(cx, function (a: string): string { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) }) } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. const m = /^[\],:{}\s]*$/.test( text .replace(/\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''), ) if (m) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. // biome-ignore lint/security/noGlobalEval: needed for PHP port const parsed = eval('(' + text + ')') setPhpRuntimeEntry('last_error_json', 0) return parsed } // usable by json_last_error() setPhpRuntimeEntry('last_error_json', 4) return null }