CVE-2026-33993
Description
Locutus brings stdlibs of other programming languages to JavaScript for educational purposes. Prior to version 3.0.25, the unserialize() function in locutus/php/var/unserialize assigns deserialized keys to plain objects via bracket notation without filtering the __proto__ key. When a PHP serialized payload contains __proto__ as an array or object key, JavaScript's __proto__ setter is invoked, replacing the deserialized object's prototype with attacker-controlled content. This enables property injection, for...in propagation of injected properties, and denial of service via built-in method override. This is distinct from the previously reported prototype pollution in parse_str (GHSA-f98m-q3hr-p5wq, GHSA-rxrv-835q-v5mh) — unserialize is a different function with no mitigation applied. Version 3.0.25 patches the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
locutusnpm | < 3.0.25 | 3.0.25 |
Affected products
1Patches
1345a6211e1e6fix: harden php prototype pollution sinks (#597)
8 files changed · +111 −20
CHANGELOG.md+5 −0 modified@@ -27,6 +27,11 @@ Ideas that will be planned and find their way into a release at one point Released: TBA. [Diff](https://github.com/locutusjs/locutus/compare/v3.0.24...main). +### Security + +- Hardened `php/var/unserialize` against `__proto__` / `constructor` / `prototype` key injection by defining those keys as plain own properties instead of letting them mutate the returned object's prototype. +- Hardened `php/strings/parse_str` against dangerous key-path prototype pollution without relying on `RegExp.prototype.test`, so `__proto__` and `constructor[prototype]` payloads are skipped even if regex guards are tampered with earlier in the process. + ### Inventory - Added a separate canonical upstream-surface scope manifest and made enumeration/checking fail on missing expected namespaces, unexpected namespaces, and source-ref drift before triage policy is applied.
docs/prompts/LOG.md+21 −0 modified@@ -2911,3 +2911,24 @@ LLMs log key learnings, progress, and next steps in one `### Iteration ${increme - Key learnings: - The queue is small enough to handle manually, but only if we treat obviously overlapping reports as clusters rather than six independent “vulnerabilities.” - For the CI/header cluster, least-privilege workflow separation removes the realistic secret-exfiltration and supply-chain pivot before we even decide whether the remaining reports deserve package-level advisory treatment. + +### Iteration 143 + +2026-03-25 + +- **Area: Security advisory triage** +- Plan: + - Triage the prototype-pollution cluster next by validating whether `unserialize` and the `parse_str` follow-up are both still real on current `main`. + - If they are, fix the runtime sinks directly before deciding final advisory disposition. +- Progress: + - Reproduced that `php/var/unserialize` still lets a serialized `__proto__` key replace the returned object's prototype, causing hidden property injection on the deserialized value. + - Reproduced that `php/strings/parse_str` can still be driven into global prototype pollution when the guard path is neutralized, so the follow-up report is not just queue noise. + - Hardened `unserialize` by routing dangerous keys through `Object.defineProperty`, preserving them as plain own properties rather than prototype setters. + - Hardened `parse_str` by skipping dangerous key paths during assignment instead of trusting a regex-prototype guard. + - Added focused regressions for both functions so the prototype-pollution cluster now has code-level coverage instead of only advisory text. +- Validation: + - `corepack yarn exec vitest run test/custom/parse_str-prototype-pollution.vitest.ts test/custom/unserialize-prototype-pollution.vitest.ts` + - direct `tsx` repro scripts for both `unserialize` and `parse_str` +- Key learnings: + - The `unserialize` report is a real own-object prototype injection issue, not just a duplicate of older `parse_str` history. + - For `parse_str`, fixing the final assignment sink is more robust than trying to win a whack-a-mole game around individual prototype-based guard helpers.
src/php/strings/parse_str.ts+28 −4 modified@@ -2,6 +2,11 @@ import { getPhpGlobalScope } from '../_helpers/_phpRuntimeState.ts' import { isPhpAssocObject, type PhpAssoc, type PhpInput } from '../_helpers/_phpTypes.ts' type ParseObject = PhpAssoc<PhpInput> +const DANGEROUS_PARSE_STR_KEYS = new Set(['__proto__', 'constructor', 'prototype']) + +function isDangerousParseStrKey(key: string): boolean { + return DANGEROUS_PARSE_STR_KEYS.has(key) +} export function parse_str(str: string, array?: ParseObject): void { // discuss at: https://locutus.io/php/parse_str/ @@ -71,10 +76,6 @@ export function parse_str(str: string, array?: ParseObject): void { key = _fixStr(tmp[0] ?? '') value = tmp.length < 2 ? '' : _fixStr(tmp[1] ?? '') - if (/__proto__|constructor|prototype/.test(key)) { - break - } - while (key.charAt(0) === ' ') { key = key.slice(1) } @@ -125,8 +126,22 @@ export function parse_str(str: string, array?: ParseObject): void { } keys[0] = primaryKey + let hasDangerousKey = false + for (const rawKey of keys) { + const normalizedKey = rawKey.replace(/^['"]/, '').replace(/['"]$/, '') + if (isDangerousParseStrKey(normalizedKey)) { + hasDangerousKey = true + break + } + } + + if (hasDangerousKey) { + continue + } + obj = target + let skipAssignment = false for (j = 0, keysLen = keys.length; j < keysLen; j++) { key = (keys[j] ?? '').replace(/^['"]/, '').replace(/['"]$/, '') lastObj = obj @@ -144,6 +159,11 @@ export function parse_str(str: string, array?: ParseObject): void { key = String(ct + 1) } + if (isDangerousParseStrKey(key)) { + skipAssignment = true + break + } + // if primitive value, replace with object const current = obj[key] if (!isPhpAssocObject(current)) { @@ -157,6 +177,10 @@ export function parse_str(str: string, array?: ParseObject): void { obj = next } + if (skipAssignment || isDangerousParseStrKey(key)) { + continue + } + lastObj[key] = value } }
src/php/var/unserialize.ts+17 −2 modified@@ -7,6 +7,21 @@ type CacheEntry = [value: UnserializedValue, offset?: number] type CacheFn = (<T extends CacheEntry>(value: T) => T) & { get: (index: number) => UnserializedValue } type ErrorMode = 'throw' | 'log' | 'silent' type UnserializeInput = string | null | undefined +const DANGEROUS_UNSERIALIZE_KEYS = new Set(['__proto__', 'constructor', 'prototype']) + +function setUnserializedProperty(target: UnserializedObject, key: string, value: UnserializedValue): void { + if (DANGEROUS_UNSERIALIZE_KEYS.has(key)) { + Object.defineProperty(target, key, { + value, + writable: true, + enumerable: true, + configurable: true, + }) + return + } + + target[key] = value +} function initCache(): CacheFn { const store: UnserializedValue[] = [] @@ -275,7 +290,7 @@ function expectObject(str: string, cache: CacheFn): ParsedResult { str = str.substr(value[1]) totalOffset += value[1] - obj[String(prop[0])] = value[0] + setUnserializedProperty(obj, String(prop[0]), value[0]) } // strict parsing, expect } after object literal @@ -355,7 +370,7 @@ function expectArrayItems( str = str.substr(item[1]) totalOffset += item[1] - items[String(key[0])] = item[0] + setUnserializedProperty(items, String(key[0]), item[0]) } if (hasContinousIndexes) {
test/custom/parse_str-prototype-pollution.vitest.ts+8 −8 modified@@ -1,6 +1,6 @@ /** * Tests that parse_str resists prototype pollution even when - * String.prototype.includes has been tampered with. + * the regex guard path has been tampered with. * * See: https://github.com/locutusjs/locutus/issues/... */ @@ -9,25 +9,25 @@ import { afterEach, describe, expect, it } from 'vitest' import { parse_str } from '../../src/php/strings/parse_str.ts' describe('parse_str prototype pollution resistance', () => { - const originalIncludes = String.prototype.includes + const originalTest = RegExp.prototype.test afterEach(() => { - // Restore includes so other tests aren't affected - String.prototype.includes = originalIncludes + // Restore RegExp.prototype.test so other tests aren't affected + RegExp.prototype.test = originalTest // Clean up any pollution that occurred // @ts-expect-error - cleaning up pollution delete Object.prototype.polluted }) - it('should block __proto__ pollution even when String.prototype.includes is overridden', () => { - String.prototype.includes = () => false + it('should block __proto__ pollution even when RegExp.prototype.test is overridden', () => { + RegExp.prototype.test = () => false const arr = {} as Record<string, unknown> parse_str('__proto__[polluted]=yes', arr) expect(({} as Record<string, unknown>).polluted).toBeUndefined() }) - it('should block constructor.prototype pollution even when String.prototype.includes is overridden', () => { - String.prototype.includes = () => false + it('should block constructor.prototype pollution even when RegExp.prototype.test is overridden', () => { + RegExp.prototype.test = () => false const arr = {} as Record<string, unknown> parse_str('constructor[prototype][polluted]=yes', arr) expect(({} as Record<string, unknown>).polluted).toBeUndefined()
test/custom/unserialize-prototype-pollution.vitest.ts+26 −0 added@@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest' +import { unserialize } from '../../src/php/var/unserialize.ts' + +describe('unserialize prototype pollution resistance', () => { + it('treats __proto__ as a plain own key on arrays/objects', () => { + const payload = 'a:2:{s:9:"__proto__";a:1:{s:7:"isAdmin";b:1;}s:4:"name";s:3:"bob";}' + const result = unserialize(payload, 'throw') as Record<string, unknown> + + expect(Object.keys(result)).toEqual(['__proto__', 'name']) + expect(Object.prototype.hasOwnProperty.call(result, '__proto__')).toBe(true) + expect(result.name).toBe('bob') + expect(result.isAdmin).toBeUndefined() + expect(Object.getPrototypeOf(result)).toBe(Object.prototype) + }) + + it('treats __proto__ as a plain own key on stdClass objects too', () => { + const payload = 'O:8:"stdClass":2:{s:9:"__proto__";a:1:{s:7:"isAdmin";b:1;}s:4:"name";s:3:"bob";}' + const result = unserialize(payload, 'throw') as Record<string, unknown> + + expect(Object.keys(result)).toEqual(['__proto__', 'name']) + expect(Object.prototype.hasOwnProperty.call(result, '__proto__')).toBe(true) + expect(result.name).toBe('bob') + expect(result.isAdmin).toBeUndefined() + expect(Object.getPrototypeOf(result)).toBe(Object.prototype) + }) +})
test/generated/php/strings/parse_str.vitest.ts+3 −3 modified@@ -11,9 +11,9 @@ const __locutus_source_fn = require('../../../../src/php/strings/parse_str.ts'). const __locutus_source_module_url = new URL("../../../../src/php/strings/parse_str.ts", import.meta.url) const __locutus_source_require = createRequire(__locutus_source_module_url) const __locutus_func_name = "parse_str" -const __locutus_module_js_code = "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.parse_str = parse_str;\nconst _phpRuntimeState_ts_1 = require(\"../_helpers/_phpRuntimeState.ts\");\nconst _phpTypes_ts_1 = require(\"../_helpers/_phpTypes.ts\");\nfunction parse_str(str, array) {\n // discuss at: https://locutus.io/php/parse_str/\n // original by: Cagri Ekin\n // improved by: Michael White (https://getsprink.com)\n // improved by: Jack\n // improved by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: stag019\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: MIO_KODUKI (https://mio-koduki.blogspot.com/)\n // reimplemented by: stag019\n // input by: Dreamer\n // input by: Zaide (https://zaidesthings.com/)\n // input by: David Pesta (https://davidpesta.com/)\n // input by: jeicquest\n // bugfixed by: Rafał Kukawski\n // note 1: When no argument is specified, will put variables in global scope.\n // note 1: When a particular argument has been passed, and the\n // note 1: returned value is different parse_str of PHP.\n // note 1: For example, a=b=c&d====c\n // example 1: var $arr = {}\n // example 1: parse_str('first=foo&second=bar', $arr)\n // example 1: var $result = $arr\n // returns 1: { first: 'foo', second: 'bar' }\n // example 2: var $arr = {}\n // example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', $arr)\n // example 2: var $result = $arr\n // returns 2: { str_a: \"Jack and Jill didn't see the well.\" }\n // example 3: var $abc = {3:'a'}\n // example 3: parse_str('a[b][\"c\"]=def&a[q]=t+5', $abc)\n // example 3: var $result = $abc\n // returns 3: {\"3\":\"a\",\"a\":{\"b\":{\"c\":\"def\"},\"q\":\"t 5\"}}\n // example 4: var $arr = {}\n // example 4: parse_str('a[][]=value', $arr)\n // example 4: var $result = $arr\n // returns 4: {\"a\":{\"0\":{\"0\":\"value\"}}}\n // example 5: var $arr = {}\n // example 5: parse_str('a=1&a[]=2', $arr)\n // example 5: var $result = $arr\n // returns 5: {\"a\":{\"0\":\"2\"}}\n const strArr = String(str).replace(/^&/, '').replace(/&$/, '').split('&');\n const sal = strArr.length;\n let i = 0;\n let j = 0;\n let ct = 0;\n let lastObj = {};\n let obj = {};\n let chr;\n let tmp = [];\n let key = '';\n let value = '';\n let postLeftBracketPos = 0;\n let keys = [];\n let keysLen = 0;\n const _fixStr = function (str) {\n return decodeURIComponent(str.replace(/\\+/g, '%20'));\n };\n const target = array || (0, _phpRuntimeState_ts_1.getPhpGlobalScope)();\n for (i = 0; i < sal; i++) {\n tmp = (strArr[i] ?? '').split('=');\n key = _fixStr(tmp[0] ?? '');\n value = tmp.length < 2 ? '' : _fixStr(tmp[1] ?? '');\n if (/__proto__|constructor|prototype/.test(key)) {\n break;\n }\n while (key.charAt(0) === ' ') {\n key = key.slice(1);\n }\n const nullByteIndex = key.indexOf('\\x00');\n if (nullByteIndex > -1) {\n key = key.slice(0, nullByteIndex);\n }\n if (key && key.charAt(0) !== '[') {\n keys = [];\n postLeftBracketPos = 0;\n for (j = 0; j < key.length; j++) {\n if (key.charAt(j) === '[' && !postLeftBracketPos) {\n postLeftBracketPos = j + 1;\n }\n else if (key.charAt(j) === ']') {\n if (postLeftBracketPos) {\n if (!keys.length) {\n keys.push(key.slice(0, postLeftBracketPos - 1));\n }\n keys.push(key.substr(postLeftBracketPos, j - postLeftBracketPos));\n postLeftBracketPos = 0;\n if (key.charAt(j + 1) !== '[') {\n break;\n }\n }\n }\n }\n if (!keys.length) {\n keys = [key];\n }\n let primaryKey = keys[0] ?? '';\n for (j = 0; j < primaryKey.length; j++) {\n chr = primaryKey.charAt(j);\n if (chr === ' ' || chr === '.' || chr === '[') {\n primaryKey = primaryKey.substring(0, j) + '_' + primaryKey.substring(j + 1);\n }\n if (chr === '[') {\n break;\n }\n }\n keys[0] = primaryKey;\n obj = target;\n for (j = 0, keysLen = keys.length; j < keysLen; j++) {\n key = (keys[j] ?? '').replace(/^['\"]/, '').replace(/['\"]$/, '');\n lastObj = obj;\n if ((key === '' || key === ' ') && j !== 0) {\n // Insert new dimension\n ct = -1;\n for (const objKey of Object.keys(obj)) {\n if (+objKey > ct && /^\\d+$/.test(objKey)) {\n ct = +objKey;\n }\n }\n key = String(ct + 1);\n }\n // if primitive value, replace with object\n const current = obj[key];\n if (!(0, _phpTypes_ts_1.isPhpAssocObject)(current)) {\n obj[key] = {};\n }\n const next = obj[key];\n if (!(0, _phpTypes_ts_1.isPhpAssocObject)(next)) {\n break;\n }\n obj = next;\n }\n lastObj[key] = value;\n }\n }\n}" -const __locutus_standalone_ts_code = "function isPhpList(value) {\n return Array.isArray(value);\n}\nfunction isObjectLike(value) {\n return typeof value === 'object' && value !== null;\n}\nfunction isPhpAssocObject(value) {\n return isObjectLike(value) && !isPhpList(value);\n}\nconst globalContext = typeof window === 'object' && window !== null ? window : typeof global === 'object' && global !== null ? global : {};\nfunction getPhpGlobalScope() {\n return globalContext;\n}\nfunction parse_str(str, array) {\n // discuss at: https://locutus.io/php/parse_str/\n // original by: Cagri Ekin\n // improved by: Michael White (https://getsprink.com)\n // improved by: Jack\n // improved by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: stag019\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: MIO_KODUKI (https://mio-koduki.blogspot.com/)\n // reimplemented by: stag019\n // input by: Dreamer\n // input by: Zaide (https://zaidesthings.com/)\n // input by: David Pesta (https://davidpesta.com/)\n // input by: jeicquest\n // bugfixed by: Rafał Kukawski\n // note 1: When no argument is specified, will put variables in global scope.\n // note 1: When a particular argument has been passed, and the\n // note 1: returned value is different parse_str of PHP.\n // note 1: For example, a=b=c&d====c\n // example 1: var $arr = {}\n // example 1: parse_str('first=foo&second=bar', $arr)\n // example 1: var $result = $arr\n // returns 1: { first: 'foo', second: 'bar' }\n // example 2: var $arr = {}\n // example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', $arr)\n // example 2: var $result = $arr\n // returns 2: { str_a: \"Jack and Jill didn't see the well.\" }\n // example 3: var $abc = {3:'a'}\n // example 3: parse_str('a[b][\"c\"]=def&a[q]=t+5', $abc)\n // example 3: var $result = $abc\n // returns 3: {\"3\":\"a\",\"a\":{\"b\":{\"c\":\"def\"},\"q\":\"t 5\"}}\n // example 4: var $arr = {}\n // example 4: parse_str('a[][]=value', $arr)\n // example 4: var $result = $arr\n // returns 4: {\"a\":{\"0\":{\"0\":\"value\"}}}\n // example 5: var $arr = {}\n // example 5: parse_str('a=1&a[]=2', $arr)\n // example 5: var $result = $arr\n // returns 5: {\"a\":{\"0\":\"2\"}}\n const strArr = String(str).replace(/^&/, '').replace(/&$/, '').split('&');\n const sal = strArr.length;\n let i = 0;\n let j = 0;\n let ct = 0;\n let lastObj = {};\n let obj = {};\n let chr;\n let tmp = [];\n let key = '';\n let value = '';\n let postLeftBracketPos = 0;\n let keys = [];\n let keysLen = 0;\n const _fixStr = function (str) {\n return decodeURIComponent(str.replace(/\\+/g, '%20'));\n };\n const target = array || getPhpGlobalScope();\n for (i = 0; i < sal; i++) {\n tmp = (strArr[i] ?? '').split('=');\n key = _fixStr(tmp[0] ?? '');\n value = tmp.length < 2 ? '' : _fixStr(tmp[1] ?? '');\n if (/__proto__|constructor|prototype/.test(key)) {\n break;\n }\n while (key.charAt(0) === ' ') {\n key = key.slice(1);\n }\n const nullByteIndex = key.indexOf('\\x00');\n if (nullByteIndex > -1) {\n key = key.slice(0, nullByteIndex);\n }\n if (key && key.charAt(0) !== '[') {\n keys = [];\n postLeftBracketPos = 0;\n for (j = 0; j < key.length; j++) {\n if (key.charAt(j) === '[' && !postLeftBracketPos) {\n postLeftBracketPos = j + 1;\n }\n else if (key.charAt(j) === ']') {\n if (postLeftBracketPos) {\n if (!keys.length) {\n keys.push(key.slice(0, postLeftBracketPos - 1));\n }\n keys.push(key.substr(postLeftBracketPos, j - postLeftBracketPos));\n postLeftBracketPos = 0;\n if (key.charAt(j + 1) !== '[') {\n break;\n }\n }\n }\n }\n if (!keys.length) {\n keys = [key];\n }\n let primaryKey = keys[0] ?? '';\n for (j = 0; j < primaryKey.length; j++) {\n chr = primaryKey.charAt(j);\n if (chr === ' ' || chr === '.' || chr === '[') {\n primaryKey = primaryKey.substring(0, j) + '_' + primaryKey.substring(j + 1);\n }\n if (chr === '[') {\n break;\n }\n }\n keys[0] = primaryKey;\n obj = target;\n for (j = 0, keysLen = keys.length; j < keysLen; j++) {\n key = (keys[j] ?? '').replace(/^['\"]/, '').replace(/['\"]$/, '');\n lastObj = obj;\n if ((key === '' || key === ' ') && j !== 0) {\n // Insert new dimension\n ct = -1;\n for (const objKey of Object.keys(obj)) {\n if (+objKey > ct && /^\\d+$/.test(objKey)) {\n ct = +objKey;\n }\n }\n key = String(ct + 1);\n }\n // if primitive value, replace with object\n const current = obj[key];\n if (!isPhpAssocObject(current)) {\n obj[key] = {};\n }\n const next = obj[key];\n if (!isPhpAssocObject(next)) {\n break;\n }\n obj = next;\n }\n lastObj[key] = value;\n }\n }\n}" -const __locutus_standalone_js_code = "// php/_helpers/_phpTypes (Locutus helper dependency)\nfunction isObjectLike(value) {\n return typeof value === 'object' && value !== null;\n}\nfunction isPhpAssocObject(value) {\n return isObjectLike(value) && !Array.isArray(value);\n}\n// php/_helpers/_phpRuntimeState (Locutus helper dependency)\nconst globalContext = typeof window === 'object' && window !== null ? window : typeof global === 'object' && global !== null ? global : {};\nfunction getPhpGlobalScope() {\n return globalContext;\n}\n// php/strings/parse_str (target function module)\nfunction parse_str(str, array) {\n // discuss at: https://locutus.io/php/parse_str/\n // original by: Cagri Ekin\n // improved by: Michael White (https://getsprink.com)\n // improved by: Jack\n // improved by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: stag019\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: MIO_KODUKI (https://mio-koduki.blogspot.com/)\n // reimplemented by: stag019\n // input by: Dreamer\n // input by: Zaide (https://zaidesthings.com/)\n // input by: David Pesta (https://davidpesta.com/)\n // input by: jeicquest\n // bugfixed by: Rafał Kukawski\n // note 1: When no argument is specified, will put variables in global scope.\n // note 1: When a particular argument has been passed, and the\n // note 1: returned value is different parse_str of PHP.\n // note 1: For example, a=b=c&d====c\n // example 1: var $arr = {}\n // example 1: parse_str('first=foo&second=bar', $arr)\n // example 1: var $result = $arr\n // returns 1: { first: 'foo', second: 'bar' }\n // example 2: var $arr = {}\n // example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', $arr)\n // example 2: var $result = $arr\n // returns 2: { str_a: \"Jack and Jill didn't see the well.\" }\n // example 3: var $abc = {3:'a'}\n // example 3: parse_str('a[b][\"c\"]=def&a[q]=t+5', $abc)\n // example 3: var $result = $abc\n // returns 3: {\"3\":\"a\",\"a\":{\"b\":{\"c\":\"def\"},\"q\":\"t 5\"}}\n // example 4: var $arr = {}\n // example 4: parse_str('a[][]=value', $arr)\n // example 4: var $result = $arr\n // returns 4: {\"a\":{\"0\":{\"0\":\"value\"}}}\n // example 5: var $arr = {}\n // example 5: parse_str('a=1&a[]=2', $arr)\n // example 5: var $result = $arr\n // returns 5: {\"a\":{\"0\":\"2\"}}\n const strArr = String(str).replace(/^&/, '').replace(/&$/, '').split('&');\n const sal = strArr.length;\n let i = 0;\n let j = 0;\n let ct = 0;\n let lastObj = {};\n let obj = {};\n let chr;\n let tmp = [];\n let key = '';\n let value = '';\n let postLeftBracketPos = 0;\n let keys = [];\n let keysLen = 0;\n const _fixStr = function (str) {\n return decodeURIComponent(str.replace(/\\+/g, '%20'));\n };\n const target = array || getPhpGlobalScope();\n for (i = 0; i < sal; i++) {\n tmp = (strArr[i] ?? '').split('=');\n key = _fixStr(tmp[0] ?? '');\n value = tmp.length < 2 ? '' : _fixStr(tmp[1] ?? '');\n if (/__proto__|constructor|prototype/.test(key)) {\n break;\n }\n while (key.charAt(0) === ' ') {\n key = key.slice(1);\n }\n const nullByteIndex = key.indexOf('\\x00');\n if (nullByteIndex > -1) {\n key = key.slice(0, nullByteIndex);\n }\n if (key && key.charAt(0) !== '[') {\n keys = [];\n postLeftBracketPos = 0;\n for (j = 0; j < key.length; j++) {\n if (key.charAt(j) === '[' && !postLeftBracketPos) {\n postLeftBracketPos = j + 1;\n }\n else if (key.charAt(j) === ']') {\n if (postLeftBracketPos) {\n if (!keys.length) {\n keys.push(key.slice(0, postLeftBracketPos - 1));\n }\n keys.push(key.substr(postLeftBracketPos, j - postLeftBracketPos));\n postLeftBracketPos = 0;\n if (key.charAt(j + 1) !== '[') {\n break;\n }\n }\n }\n }\n if (!keys.length) {\n keys = [key];\n }\n let primaryKey = keys[0] ?? '';\n for (j = 0; j < primaryKey.length; j++) {\n chr = primaryKey.charAt(j);\n if (chr === ' ' || chr === '.' || chr === '[') {\n primaryKey = primaryKey.substring(0, j) + '_' + primaryKey.substring(j + 1);\n }\n if (chr === '[') {\n break;\n }\n }\n keys[0] = primaryKey;\n obj = target;\n for (j = 0, keysLen = keys.length; j < keysLen; j++) {\n key = (keys[j] ?? '').replace(/^['\"]/, '').replace(/['\"]$/, '');\n lastObj = obj;\n if ((key === '' || key === ' ') && j !== 0) {\n // Insert new dimension\n ct = -1;\n for (const objKey of Object.keys(obj)) {\n if (+objKey > ct && /^\\d+$/.test(objKey)) {\n ct = +objKey;\n }\n }\n key = String(ct + 1);\n }\n // if primitive value, replace with object\n const current = obj[key];\n if (!isPhpAssocObject(current)) {\n obj[key] = {};\n }\n const next = obj[key];\n if (!isPhpAssocObject(next)) {\n break;\n }\n obj = next;\n }\n lastObj[key] = value;\n }\n }\n}" +const __locutus_module_js_code = "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.parse_str = parse_str;\nconst _phpRuntimeState_ts_1 = require(\"../_helpers/_phpRuntimeState.ts\");\nconst _phpTypes_ts_1 = require(\"../_helpers/_phpTypes.ts\");\nconst DANGEROUS_PARSE_STR_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\nfunction isDangerousParseStrKey(key) {\n return DANGEROUS_PARSE_STR_KEYS.has(key);\n}\nfunction parse_str(str, array) {\n // discuss at: https://locutus.io/php/parse_str/\n // original by: Cagri Ekin\n // improved by: Michael White (https://getsprink.com)\n // improved by: Jack\n // improved by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: stag019\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: MIO_KODUKI (https://mio-koduki.blogspot.com/)\n // reimplemented by: stag019\n // input by: Dreamer\n // input by: Zaide (https://zaidesthings.com/)\n // input by: David Pesta (https://davidpesta.com/)\n // input by: jeicquest\n // bugfixed by: Rafał Kukawski\n // note 1: When no argument is specified, will put variables in global scope.\n // note 1: When a particular argument has been passed, and the\n // note 1: returned value is different parse_str of PHP.\n // note 1: For example, a=b=c&d====c\n // example 1: var $arr = {}\n // example 1: parse_str('first=foo&second=bar', $arr)\n // example 1: var $result = $arr\n // returns 1: { first: 'foo', second: 'bar' }\n // example 2: var $arr = {}\n // example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', $arr)\n // example 2: var $result = $arr\n // returns 2: { str_a: \"Jack and Jill didn't see the well.\" }\n // example 3: var $abc = {3:'a'}\n // example 3: parse_str('a[b][\"c\"]=def&a[q]=t+5', $abc)\n // example 3: var $result = $abc\n // returns 3: {\"3\":\"a\",\"a\":{\"b\":{\"c\":\"def\"},\"q\":\"t 5\"}}\n // example 4: var $arr = {}\n // example 4: parse_str('a[][]=value', $arr)\n // example 4: var $result = $arr\n // returns 4: {\"a\":{\"0\":{\"0\":\"value\"}}}\n // example 5: var $arr = {}\n // example 5: parse_str('a=1&a[]=2', $arr)\n // example 5: var $result = $arr\n // returns 5: {\"a\":{\"0\":\"2\"}}\n const strArr = String(str).replace(/^&/, '').replace(/&$/, '').split('&');\n const sal = strArr.length;\n let i = 0;\n let j = 0;\n let ct = 0;\n let lastObj = {};\n let obj = {};\n let chr;\n let tmp = [];\n let key = '';\n let value = '';\n let postLeftBracketPos = 0;\n let keys = [];\n let keysLen = 0;\n const _fixStr = function (str) {\n return decodeURIComponent(str.replace(/\\+/g, '%20'));\n };\n const target = array || (0, _phpRuntimeState_ts_1.getPhpGlobalScope)();\n for (i = 0; i < sal; i++) {\n tmp = (strArr[i] ?? '').split('=');\n key = _fixStr(tmp[0] ?? '');\n value = tmp.length < 2 ? '' : _fixStr(tmp[1] ?? '');\n while (key.charAt(0) === ' ') {\n key = key.slice(1);\n }\n const nullByteIndex = key.indexOf('\\x00');\n if (nullByteIndex > -1) {\n key = key.slice(0, nullByteIndex);\n }\n if (key && key.charAt(0) !== '[') {\n keys = [];\n postLeftBracketPos = 0;\n for (j = 0; j < key.length; j++) {\n if (key.charAt(j) === '[' && !postLeftBracketPos) {\n postLeftBracketPos = j + 1;\n }\n else if (key.charAt(j) === ']') {\n if (postLeftBracketPos) {\n if (!keys.length) {\n keys.push(key.slice(0, postLeftBracketPos - 1));\n }\n keys.push(key.substr(postLeftBracketPos, j - postLeftBracketPos));\n postLeftBracketPos = 0;\n if (key.charAt(j + 1) !== '[') {\n break;\n }\n }\n }\n }\n if (!keys.length) {\n keys = [key];\n }\n let primaryKey = keys[0] ?? '';\n for (j = 0; j < primaryKey.length; j++) {\n chr = primaryKey.charAt(j);\n if (chr === ' ' || chr === '.' || chr === '[') {\n primaryKey = primaryKey.substring(0, j) + '_' + primaryKey.substring(j + 1);\n }\n if (chr === '[') {\n break;\n }\n }\n keys[0] = primaryKey;\n let hasDangerousKey = false;\n for (const rawKey of keys) {\n const normalizedKey = rawKey.replace(/^['\"]/, '').replace(/['\"]$/, '');\n if (isDangerousParseStrKey(normalizedKey)) {\n hasDangerousKey = true;\n break;\n }\n }\n if (hasDangerousKey) {\n continue;\n }\n obj = target;\n let skipAssignment = false;\n for (j = 0, keysLen = keys.length; j < keysLen; j++) {\n key = (keys[j] ?? '').replace(/^['\"]/, '').replace(/['\"]$/, '');\n lastObj = obj;\n if ((key === '' || key === ' ') && j !== 0) {\n // Insert new dimension\n ct = -1;\n for (const objKey of Object.keys(obj)) {\n if (+objKey > ct && /^\\d+$/.test(objKey)) {\n ct = +objKey;\n }\n }\n key = String(ct + 1);\n }\n if (isDangerousParseStrKey(key)) {\n skipAssignment = true;\n break;\n }\n // if primitive value, replace with object\n const current = obj[key];\n if (!(0, _phpTypes_ts_1.isPhpAssocObject)(current)) {\n obj[key] = {};\n }\n const next = obj[key];\n if (!(0, _phpTypes_ts_1.isPhpAssocObject)(next)) {\n break;\n }\n obj = next;\n }\n if (skipAssignment || isDangerousParseStrKey(key)) {\n continue;\n }\n lastObj[key] = value;\n }\n }\n}" +const __locutus_standalone_ts_code = "function isPhpList(value) {\n return Array.isArray(value);\n}\nfunction isObjectLike(value) {\n return typeof value === 'object' && value !== null;\n}\nfunction isPhpAssocObject(value) {\n return isObjectLike(value) && !isPhpList(value);\n}\nconst globalContext = typeof window === 'object' && window !== null ? window : typeof global === 'object' && global !== null ? global : {};\nfunction getPhpGlobalScope() {\n return globalContext;\n}\nconst DANGEROUS_PARSE_STR_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\nfunction isDangerousParseStrKey(key) {\n return DANGEROUS_PARSE_STR_KEYS.has(key);\n}\nfunction parse_str(str, array) {\n // discuss at: https://locutus.io/php/parse_str/\n // original by: Cagri Ekin\n // improved by: Michael White (https://getsprink.com)\n // improved by: Jack\n // improved by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: stag019\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: MIO_KODUKI (https://mio-koduki.blogspot.com/)\n // reimplemented by: stag019\n // input by: Dreamer\n // input by: Zaide (https://zaidesthings.com/)\n // input by: David Pesta (https://davidpesta.com/)\n // input by: jeicquest\n // bugfixed by: Rafał Kukawski\n // note 1: When no argument is specified, will put variables in global scope.\n // note 1: When a particular argument has been passed, and the\n // note 1: returned value is different parse_str of PHP.\n // note 1: For example, a=b=c&d====c\n // example 1: var $arr = {}\n // example 1: parse_str('first=foo&second=bar', $arr)\n // example 1: var $result = $arr\n // returns 1: { first: 'foo', second: 'bar' }\n // example 2: var $arr = {}\n // example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', $arr)\n // example 2: var $result = $arr\n // returns 2: { str_a: \"Jack and Jill didn't see the well.\" }\n // example 3: var $abc = {3:'a'}\n // example 3: parse_str('a[b][\"c\"]=def&a[q]=t+5', $abc)\n // example 3: var $result = $abc\n // returns 3: {\"3\":\"a\",\"a\":{\"b\":{\"c\":\"def\"},\"q\":\"t 5\"}}\n // example 4: var $arr = {}\n // example 4: parse_str('a[][]=value', $arr)\n // example 4: var $result = $arr\n // returns 4: {\"a\":{\"0\":{\"0\":\"value\"}}}\n // example 5: var $arr = {}\n // example 5: parse_str('a=1&a[]=2', $arr)\n // example 5: var $result = $arr\n // returns 5: {\"a\":{\"0\":\"2\"}}\n const strArr = String(str).replace(/^&/, '').replace(/&$/, '').split('&');\n const sal = strArr.length;\n let i = 0;\n let j = 0;\n let ct = 0;\n let lastObj = {};\n let obj = {};\n let chr;\n let tmp = [];\n let key = '';\n let value = '';\n let postLeftBracketPos = 0;\n let keys = [];\n let keysLen = 0;\n const _fixStr = function (str) {\n return decodeURIComponent(str.replace(/\\+/g, '%20'));\n };\n const target = array || getPhpGlobalScope();\n for (i = 0; i < sal; i++) {\n tmp = (strArr[i] ?? '').split('=');\n key = _fixStr(tmp[0] ?? '');\n value = tmp.length < 2 ? '' : _fixStr(tmp[1] ?? '');\n while (key.charAt(0) === ' ') {\n key = key.slice(1);\n }\n const nullByteIndex = key.indexOf('\\x00');\n if (nullByteIndex > -1) {\n key = key.slice(0, nullByteIndex);\n }\n if (key && key.charAt(0) !== '[') {\n keys = [];\n postLeftBracketPos = 0;\n for (j = 0; j < key.length; j++) {\n if (key.charAt(j) === '[' && !postLeftBracketPos) {\n postLeftBracketPos = j + 1;\n }\n else if (key.charAt(j) === ']') {\n if (postLeftBracketPos) {\n if (!keys.length) {\n keys.push(key.slice(0, postLeftBracketPos - 1));\n }\n keys.push(key.substr(postLeftBracketPos, j - postLeftBracketPos));\n postLeftBracketPos = 0;\n if (key.charAt(j + 1) !== '[') {\n break;\n }\n }\n }\n }\n if (!keys.length) {\n keys = [key];\n }\n let primaryKey = keys[0] ?? '';\n for (j = 0; j < primaryKey.length; j++) {\n chr = primaryKey.charAt(j);\n if (chr === ' ' || chr === '.' || chr === '[') {\n primaryKey = primaryKey.substring(0, j) + '_' + primaryKey.substring(j + 1);\n }\n if (chr === '[') {\n break;\n }\n }\n keys[0] = primaryKey;\n let hasDangerousKey = false;\n for (const rawKey of keys) {\n const normalizedKey = rawKey.replace(/^['\"]/, '').replace(/['\"]$/, '');\n if (isDangerousParseStrKey(normalizedKey)) {\n hasDangerousKey = true;\n break;\n }\n }\n if (hasDangerousKey) {\n continue;\n }\n obj = target;\n let skipAssignment = false;\n for (j = 0, keysLen = keys.length; j < keysLen; j++) {\n key = (keys[j] ?? '').replace(/^['\"]/, '').replace(/['\"]$/, '');\n lastObj = obj;\n if ((key === '' || key === ' ') && j !== 0) {\n // Insert new dimension\n ct = -1;\n for (const objKey of Object.keys(obj)) {\n if (+objKey > ct && /^\\d+$/.test(objKey)) {\n ct = +objKey;\n }\n }\n key = String(ct + 1);\n }\n if (isDangerousParseStrKey(key)) {\n skipAssignment = true;\n break;\n }\n // if primitive value, replace with object\n const current = obj[key];\n if (!isPhpAssocObject(current)) {\n obj[key] = {};\n }\n const next = obj[key];\n if (!isPhpAssocObject(next)) {\n break;\n }\n obj = next;\n }\n if (skipAssignment || isDangerousParseStrKey(key)) {\n continue;\n }\n lastObj[key] = value;\n }\n }\n}" +const __locutus_standalone_js_code = "// php/_helpers/_phpTypes (Locutus helper dependency)\nfunction isObjectLike(value) {\n return typeof value === 'object' && value !== null;\n}\nfunction isPhpAssocObject(value) {\n return isObjectLike(value) && !Array.isArray(value);\n}\n// php/_helpers/_phpRuntimeState (Locutus helper dependency)\nconst globalContext = typeof window === 'object' && window !== null ? window : typeof global === 'object' && global !== null ? global : {};\nfunction getPhpGlobalScope() {\n return globalContext;\n}\n// php/strings/parse_str (target function module)\nconst DANGEROUS_PARSE_STR_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\nfunction parse_str(str, array) {\n // discuss at: https://locutus.io/php/parse_str/\n // original by: Cagri Ekin\n // improved by: Michael White (https://getsprink.com)\n // improved by: Jack\n // improved by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: stag019\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: MIO_KODUKI (https://mio-koduki.blogspot.com/)\n // reimplemented by: stag019\n // input by: Dreamer\n // input by: Zaide (https://zaidesthings.com/)\n // input by: David Pesta (https://davidpesta.com/)\n // input by: jeicquest\n // bugfixed by: Rafał Kukawski\n // note 1: When no argument is specified, will put variables in global scope.\n // note 1: When a particular argument has been passed, and the\n // note 1: returned value is different parse_str of PHP.\n // note 1: For example, a=b=c&d====c\n // example 1: var $arr = {}\n // example 1: parse_str('first=foo&second=bar', $arr)\n // example 1: var $result = $arr\n // returns 1: { first: 'foo', second: 'bar' }\n // example 2: var $arr = {}\n // example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', $arr)\n // example 2: var $result = $arr\n // returns 2: { str_a: \"Jack and Jill didn't see the well.\" }\n // example 3: var $abc = {3:'a'}\n // example 3: parse_str('a[b][\"c\"]=def&a[q]=t+5', $abc)\n // example 3: var $result = $abc\n // returns 3: {\"3\":\"a\",\"a\":{\"b\":{\"c\":\"def\"},\"q\":\"t 5\"}}\n // example 4: var $arr = {}\n // example 4: parse_str('a[][]=value', $arr)\n // example 4: var $result = $arr\n // returns 4: {\"a\":{\"0\":{\"0\":\"value\"}}}\n // example 5: var $arr = {}\n // example 5: parse_str('a=1&a[]=2', $arr)\n // example 5: var $result = $arr\n // returns 5: {\"a\":{\"0\":\"2\"}}\n const strArr = String(str).replace(/^&/, '').replace(/&$/, '').split('&');\n const sal = strArr.length;\n let i = 0;\n let j = 0;\n let ct = 0;\n let lastObj = {};\n let obj = {};\n let chr;\n let tmp = [];\n let key = '';\n let value = '';\n let postLeftBracketPos = 0;\n let keys = [];\n let keysLen = 0;\n const _fixStr = function (str) {\n return decodeURIComponent(str.replace(/\\+/g, '%20'));\n };\n const target = array || getPhpGlobalScope();\n for (i = 0; i < sal; i++) {\n tmp = (strArr[i] ?? '').split('=');\n key = _fixStr(tmp[0] ?? '');\n value = tmp.length < 2 ? '' : _fixStr(tmp[1] ?? '');\n while (key.charAt(0) === ' ') {\n key = key.slice(1);\n }\n const nullByteIndex = key.indexOf('\\x00');\n if (nullByteIndex > -1) {\n key = key.slice(0, nullByteIndex);\n }\n if (key && key.charAt(0) !== '[') {\n keys = [];\n postLeftBracketPos = 0;\n for (j = 0; j < key.length; j++) {\n if (key.charAt(j) === '[' && !postLeftBracketPos) {\n postLeftBracketPos = j + 1;\n }\n else if (key.charAt(j) === ']') {\n if (postLeftBracketPos) {\n if (!keys.length) {\n keys.push(key.slice(0, postLeftBracketPos - 1));\n }\n keys.push(key.substr(postLeftBracketPos, j - postLeftBracketPos));\n postLeftBracketPos = 0;\n if (key.charAt(j + 1) !== '[') {\n break;\n }\n }\n }\n }\n if (!keys.length) {\n keys = [key];\n }\n let primaryKey = keys[0] ?? '';\n for (j = 0; j < primaryKey.length; j++) {\n chr = primaryKey.charAt(j);\n if (chr === ' ' || chr === '.' || chr === '[') {\n primaryKey = primaryKey.substring(0, j) + '_' + primaryKey.substring(j + 1);\n }\n if (chr === '[') {\n break;\n }\n }\n keys[0] = primaryKey;\n let hasDangerousKey = false;\n for (const rawKey of keys) {\n const normalizedKey = rawKey.replace(/^['\"]/, '').replace(/['\"]$/, '');\n if (DANGEROUS_PARSE_STR_KEYS.has(normalizedKey)) {\n hasDangerousKey = true;\n break;\n }\n }\n if (hasDangerousKey) {\n continue;\n }\n obj = target;\n let skipAssignment = false;\n for (j = 0, keysLen = keys.length; j < keysLen; j++) {\n key = (keys[j] ?? '').replace(/^['\"]/, '').replace(/['\"]$/, '');\n lastObj = obj;\n if ((key === '' || key === ' ') && j !== 0) {\n // Insert new dimension\n ct = -1;\n for (const objKey of Object.keys(obj)) {\n if (+objKey > ct && /^\\d+$/.test(objKey)) {\n ct = +objKey;\n }\n }\n key = String(ct + 1);\n }\n if (DANGEROUS_PARSE_STR_KEYS.has(key)) {\n skipAssignment = true;\n break;\n }\n // if primitive value, replace with object\n const current = obj[key];\n if (!isPhpAssocObject(current)) {\n obj[key] = {};\n }\n const next = obj[key];\n if (!isPhpAssocObject(next)) {\n break;\n }\n obj = next;\n }\n if (skipAssignment || DANGEROUS_PARSE_STR_KEYS.has(key)) {\n continue;\n }\n lastObj[key] = value;\n }\n }\n}" const __locutus_eval_function = (compiledCode: string): ((...args: unknown[]) => unknown) => { const evaluator = new Function('require', compiledCode + '\nreturn ' + __locutus_func_name + ';')
test/generated/php/var/unserialize.vitest.ts+3 −3 modified@@ -11,9 +11,9 @@ const __locutus_source_fn = require('../../../../src/php/var/unserialize.ts').un const __locutus_source_module_url = new URL("../../../../src/php/var/unserialize.ts", import.meta.url) const __locutus_source_require = createRequire(__locutus_source_module_url) const __locutus_func_name = "unserialize" -const __locutus_module_js_code = "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.unserialize = unserialize;\nfunction initCache() {\n const store = [];\n // cache only first element, second is length to jump ahead for the parser\n const cacheBase = function cache(value) {\n store.push(value[0]);\n return value;\n };\n const cache = Object.assign(cacheBase, {\n get: (index) => {\n if (index >= store.length) {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n const cachedValue = store[index];\n if (typeof cachedValue === 'undefined') {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n return cachedValue;\n },\n });\n return cache;\n}\nfunction expectType(str, cache) {\n const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g;\n const type = (types.exec(str) || [])[0];\n if (!type) {\n throw new SyntaxError('Invalid input: ' + str);\n }\n switch (type) {\n case 'N':\n return cache([null, 2]);\n case 'b':\n return cache(expectBool(str));\n case 'i':\n return cache(expectInt(str));\n case 'd':\n return cache(expectFloat(str));\n case 's':\n return cache(expectString(str));\n case 'S':\n return cache(expectEscapedString(str));\n case 'a':\n return expectArray(str, cache);\n case 'O':\n return expectObject(str, cache);\n case 'C':\n return expectClass(str, cache);\n case 'r':\n case 'R':\n return expectReference(str, cache);\n default:\n throw new SyntaxError(`Invalid or unsupported data type: ${type}`);\n }\n}\nfunction expectBool(str) {\n const reBool = /^b:([01]);/;\n const [match, boolMatch] = reBool.exec(str) || [];\n if (!match || !boolMatch) {\n throw new SyntaxError('Invalid bool value, expected 0 or 1');\n }\n return [boolMatch === '1', match.length];\n}\nfunction expectInt(str) {\n const reInt = /^i:([+-]?\\d+);/;\n const [match, intMatch] = reInt.exec(str) || [];\n if (!match || !intMatch) {\n throw new SyntaxError('Expected an integer value');\n }\n return [parseInt(intMatch, 10), match.length];\n}\nfunction expectFloat(str) {\n const reFloat = /^d:(NAN|-?INF|(?:\\d+\\.\\d*|\\d*\\.\\d+|\\d+)(?:[eE][+-]\\d+)?);/;\n const [match, floatMatch] = reFloat.exec(str) || [];\n if (!match || !floatMatch) {\n throw new SyntaxError('Expected a float value');\n }\n let floatValue = 0;\n switch (floatMatch) {\n case 'NAN':\n floatValue = Number.NaN;\n break;\n case '-INF':\n floatValue = Number.NEGATIVE_INFINITY;\n break;\n case 'INF':\n floatValue = Number.POSITIVE_INFINITY;\n break;\n default:\n floatValue = parseFloat(floatMatch);\n break;\n }\n return [floatValue, match.length];\n}\nfunction readBytes(str, len, escapedString = false) {\n let bytes = 0;\n let out = '';\n let c = 0;\n const strLen = str.length;\n let wasHighSurrogate = false;\n let escapedChars = 0;\n while (bytes < len && c < strLen) {\n let chr = str.charAt(c);\n const code = chr.charCodeAt(0);\n const isHighSurrogate = code >= 0xd800 && code <= 0xdbff;\n const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff;\n if (escapedString && chr === '\\\\') {\n chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16));\n escapedChars++;\n // each escaped sequence is 3 characters. Go 2 chars ahead.\n // third character will be jumped over a few lines later\n c += 2;\n }\n c++;\n bytes +=\n isHighSurrogate || (isLowSurrogate && wasHighSurrogate)\n ? // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate\n // if low surrogate preceded by high surrogate, add 2 bytes\n 2\n : code > 0x7ff\n ? // otherwise low surrogate falls into this part\n 3\n : code > 0x7f\n ? 2\n : 1;\n // if high surrogate is not followed by low surrogate, add 1 more byte\n bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0;\n out += chr;\n wasHighSurrogate = isHighSurrogate;\n }\n return [out, bytes, escapedChars];\n}\nfunction expectString(str) {\n // PHP strings consist of one-byte characters.\n // JS uses 2 bytes with possible surrogate pairs.\n // Serialized length of 2 is still 1 JS string character\n const reStrLength = /^s:(\\d+):\"/g; // also match the opening \" char\n const [match, byteLenMatch] = reStrLength.exec(str) || [];\n if (!match || !byteLenMatch) {\n throw new SyntaxError('Expected a string value');\n }\n const len = parseInt(byteLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes] = readBytes(str, len);\n if (bytes !== len) {\n throw new SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectEscapedString(str) {\n const reStrLength = /^S:(\\d+):\"/g; // also match the opening \" char\n const [match, strLenMatch] = reStrLength.exec(str) || [];\n if (!match || !strLenMatch) {\n throw new SyntaxError('Expected an escaped string value');\n }\n const len = parseInt(strLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes, escapedChars] = readBytes(str, len, true);\n if (bytes !== len) {\n throw new SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length + escapedChars * 2);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectKeyOrIndex(str) {\n try {\n return expectString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectEscapedString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectInt(str);\n }\n catch (_err) {\n throw new SyntaxError('Expected key or index');\n }\n}\nfunction expectObject(str, cache) {\n // O:<class name length>:\"class name\":<prop count>:{<props and values>}\n // O:8:\"stdClass\":2:{s:3:\"foo\";s:3:\"bar\";s:3:\"bar\";s:3:\"baz\";}\n const reObjectLiteral = /^O:(\\d+):\"([^\"]+)\":(\\d+):\\{/;\n const [objectLiteralBeginMatch /* classNameLengthMatch */, , className, propCountMatch] = reObjectLiteral.exec(str) || [];\n if (!objectLiteralBeginMatch || !propCountMatch) {\n throw new SyntaxError('Invalid input');\n }\n if (className !== 'stdClass') {\n throw new SyntaxError(`Unsupported object type: ${className}`);\n }\n let totalOffset = objectLiteralBeginMatch.length;\n const propCount = parseInt(propCountMatch, 10);\n const obj = {};\n cache([obj]);\n str = str.substr(totalOffset);\n for (let i = 0; i < propCount; i++) {\n const prop = expectKeyOrIndex(str);\n str = str.substr(prop[1]);\n totalOffset += prop[1];\n const value = expectType(str, cache);\n str = str.substr(value[1]);\n totalOffset += value[1];\n obj[String(prop[0])] = value[0];\n }\n // strict parsing, expect } after object literal\n if (str.charAt(0) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [obj, totalOffset + 1]; // skip final }\n}\nfunction expectClass(_str, _cache) {\n // can't be well supported, because requires calling eval (or similar)\n // in order to call serialized constructor name\n // which is unsafe\n // or assume that constructor is defined in global scope\n // but this is too much limiting\n throw new Error('Not yet implemented');\n}\nfunction expectReference(str, cache) {\n const reRef = /^[rR]:([1-9]\\d*);/;\n const [match, refIndex] = reRef.exec(str) || [];\n if (!match || !refIndex) {\n throw new SyntaxError('Expected reference value');\n }\n return [cache.get(parseInt(refIndex, 10) - 1), match.length];\n}\nfunction expectArray(str, cache) {\n const reArrayLength = /^a:(\\d+):{/;\n const [arrayLiteralBeginMatch, arrayLengthMatch] = reArrayLength.exec(str) || [];\n if (!arrayLiteralBeginMatch || !arrayLengthMatch) {\n throw new SyntaxError('Expected array length annotation');\n }\n str = str.substr(arrayLiteralBeginMatch.length);\n const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache);\n // strict parsing, expect closing } brace after array literal\n if (str.charAt(array[1]) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [array[0], arrayLiteralBeginMatch.length + array[1] + 1]; // jump over }\n}\nfunction expectArrayItems(str, expectedItems = 0, cache) {\n let key;\n let item;\n let totalOffset = 0;\n let hasContinousIndexes = true;\n let lastIndex = -1;\n const items = {};\n cache([items]);\n for (let i = 0; i < expectedItems; i++) {\n key = expectKeyOrIndex(str);\n hasContinousIndexes = hasContinousIndexes && typeof key[0] === 'number' && key[0] === lastIndex + 1;\n lastIndex = typeof key[0] === 'number' ? key[0] : lastIndex;\n str = str.substr(key[1]);\n totalOffset += key[1];\n // references are resolved immediately, so if duplicate key overwrites previous array index\n // the old value is anyway resolved\n // fixme: but next time the same reference should point to the new value\n item = expectType(str, cache);\n str = str.substr(item[1]);\n totalOffset += item[1];\n items[String(key[0])] = item[0];\n }\n if (hasContinousIndexes) {\n return [Object.values(items), totalOffset];\n }\n return [items, totalOffset];\n}\n// errorMode: 'throw', 'log', 'silent'\nfunction unserialize(str, errorMode = 'log') {\n // discuss at: https://locutus.io/php/unserialize/\n // original by: Arpad Ray (mailto:arpad@php.net)\n // improved by: Pedro Tainha (https://www.pedrotainha.com)\n // improved by: Kevin van Zonneveld (https://kvz.io)\n // improved by: Kevin van Zonneveld (https://kvz.io)\n // improved by: Chris\n // improved by: James\n // improved by: Le Torbi\n // improved by: Eli Skeggs\n // bugfixed by: dptr1988\n // bugfixed by: Kevin van Zonneveld (https://kvz.io)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: philippsimon (https://github.com/philippsimon/)\n // revised by: d3x\n // input by: Brett Zamir (https://brett-zamir.me)\n // input by: Martin (https://www.erlenwiese.de/)\n // input by: kilops\n // input by: Jaroslaw Czarniak\n // input by: lovasoa (https://github.com/lovasoa/)\n // improved by: Rafał Kukawski\n // reimplemented by: Rafał Kukawski\n // note 1: We feel the main purpose of this function should be\n // note 1: to ease the transport of data between php & js\n // note 1: Aiming for PHP-compatibility, we have to translate objects to arrays\n // example 1: unserialize('a:3:{i:0;s:5:\"Kevin\";i:1;s:3:\"van\";i:2;s:9:\"Zonneveld\";}')\n // returns 1: ['Kevin', 'van', 'Zonneveld']\n // example 2: unserialize('a:2:{s:9:\"firstName\";s:5:\"Kevin\";s:7:\"midName\";s:3:\"van\";}')\n // returns 2: {firstName: 'Kevin', midName: 'van'}\n // example 3: unserialize('a:3:{s:2:\"ü\";s:2:\"ü\";s:3:\"四\";s:3:\"四\";s:4:\"𠜎\";s:4:\"𠜎\";}')\n // returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}\n // example 4: unserialize(undefined)\n // returns 4: false\n // example 5: unserialize('O:8:\"stdClass\":1:{s:3:\"foo\";b:1;}')\n // returns 5: { foo: true }\n // example 6: unserialize('a:2:{i:0;N;i:1;s:0:\"\";}')\n // returns 6: [null, \"\"]\n // example 7: unserialize('S:7:\"\\\\65\\\\73\\\\63\\\\61\\\\70\\\\65\\\\64\";')\n // returns 7: 'escaped'\n try {\n if (typeof str !== 'string') {\n return false;\n }\n return expectType(str, initCache())[0];\n }\n catch (err) {\n if (errorMode === 'throw') {\n throw err;\n }\n else if (errorMode === 'log') {\n console.error(err);\n }\n // if silent mode we do nothing\n return false;\n }\n}" -const __locutus_standalone_ts_code = "function initCache() {\n const store = [];\n // cache only first element, second is length to jump ahead for the parser\n const cacheBase = function cache(value) {\n store.push(value[0]);\n return value;\n };\n const cache = Object.assign(cacheBase, {\n get: (index) => {\n if (index >= store.length) {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n const cachedValue = store[index];\n if (typeof cachedValue === 'undefined') {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n return cachedValue;\n },\n });\n return cache;\n}\nfunction expectType(str, cache) {\n const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g;\n const type = (types.exec(str) || [])[0];\n if (!type) {\n throw new SyntaxError('Invalid input: ' + str);\n }\n switch (type) {\n case 'N':\n return cache([null, 2]);\n case 'b':\n return cache(expectBool(str));\n case 'i':\n return cache(expectInt(str));\n case 'd':\n return cache(expectFloat(str));\n case 's':\n return cache(expectString(str));\n case 'S':\n return cache(expectEscapedString(str));\n case 'a':\n return expectArray(str, cache);\n case 'O':\n return expectObject(str, cache);\n case 'C':\n return expectClass(str, cache);\n case 'r':\n case 'R':\n return expectReference(str, cache);\n default:\n throw new SyntaxError(`Invalid or unsupported data type: ${type}`);\n }\n}\nfunction expectBool(str) {\n const reBool = /^b:([01]);/;\n const [match, boolMatch] = reBool.exec(str) || [];\n if (!match || !boolMatch) {\n throw new SyntaxError('Invalid bool value, expected 0 or 1');\n }\n return [boolMatch === '1', match.length];\n}\nfunction expectInt(str) {\n const reInt = /^i:([+-]?\\d+);/;\n const [match, intMatch] = reInt.exec(str) || [];\n if (!match || !intMatch) {\n throw new SyntaxError('Expected an integer value');\n }\n return [parseInt(intMatch, 10), match.length];\n}\nfunction expectFloat(str) {\n const reFloat = /^d:(NAN|-?INF|(?:\\d+\\.\\d*|\\d*\\.\\d+|\\d+)(?:[eE][+-]\\d+)?);/;\n const [match, floatMatch] = reFloat.exec(str) || [];\n if (!match || !floatMatch) {\n throw new SyntaxError('Expected a float value');\n }\n let floatValue = 0;\n switch (floatMatch) {\n case 'NAN':\n floatValue = Number.NaN;\n break;\n case '-INF':\n floatValue = Number.NEGATIVE_INFINITY;\n break;\n case 'INF':\n floatValue = Number.POSITIVE_INFINITY;\n break;\n default:\n floatValue = parseFloat(floatMatch);\n break;\n }\n return [floatValue, match.length];\n}\nfunction readBytes(str, len, escapedString = false) {\n let bytes = 0;\n let out = '';\n let c = 0;\n const strLen = str.length;\n let wasHighSurrogate = false;\n let escapedChars = 0;\n while (bytes < len && c < strLen) {\n let chr = str.charAt(c);\n const code = chr.charCodeAt(0);\n const isHighSurrogate = code >= 0xd800 && code <= 0xdbff;\n const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff;\n if (escapedString && chr === '\\\\') {\n chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16));\n escapedChars++;\n // each escaped sequence is 3 characters. Go 2 chars ahead.\n // third character will be jumped over a few lines later\n c += 2;\n }\n c++;\n bytes +=\n isHighSurrogate || (isLowSurrogate && wasHighSurrogate)\n ? // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate\n // if low surrogate preceded by high surrogate, add 2 bytes\n 2\n : code > 0x7ff\n ? // otherwise low surrogate falls into this part\n 3\n : code > 0x7f\n ? 2\n : 1;\n // if high surrogate is not followed by low surrogate, add 1 more byte\n bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0;\n out += chr;\n wasHighSurrogate = isHighSurrogate;\n }\n return [out, bytes, escapedChars];\n}\nfunction expectString(str) {\n // PHP strings consist of one-byte characters.\n // JS uses 2 bytes with possible surrogate pairs.\n // Serialized length of 2 is still 1 JS string character\n const reStrLength = /^s:(\\d+):\"/g; // also match the opening \" char\n const [match, byteLenMatch] = reStrLength.exec(str) || [];\n if (!match || !byteLenMatch) {\n throw new SyntaxError('Expected a string value');\n }\n const len = parseInt(byteLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes] = readBytes(str, len);\n if (bytes !== len) {\n throw new SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectEscapedString(str) {\n const reStrLength = /^S:(\\d+):\"/g; // also match the opening \" char\n const [match, strLenMatch] = reStrLength.exec(str) || [];\n if (!match || !strLenMatch) {\n throw new SyntaxError('Expected an escaped string value');\n }\n const len = parseInt(strLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes, escapedChars] = readBytes(str, len, true);\n if (bytes !== len) {\n throw new SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length + escapedChars * 2);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectKeyOrIndex(str) {\n try {\n return expectString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectEscapedString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectInt(str);\n }\n catch (_err) {\n throw new SyntaxError('Expected key or index');\n }\n}\nfunction expectObject(str, cache) {\n // O:<class name length>:\"class name\":<prop count>:{<props and values>}\n // O:8:\"stdClass\":2:{s:3:\"foo\";s:3:\"bar\";s:3:\"bar\";s:3:\"baz\";}\n const reObjectLiteral = /^O:(\\d+):\"([^\"]+)\":(\\d+):\\{/;\n const [objectLiteralBeginMatch /* classNameLengthMatch */, , className, propCountMatch] = reObjectLiteral.exec(str) || [];\n if (!objectLiteralBeginMatch || !propCountMatch) {\n throw new SyntaxError('Invalid input');\n }\n if (className !== 'stdClass') {\n throw new SyntaxError(`Unsupported object type: ${className}`);\n }\n let totalOffset = objectLiteralBeginMatch.length;\n const propCount = parseInt(propCountMatch, 10);\n const obj = {};\n cache([obj]);\n str = str.substr(totalOffset);\n for (let i = 0; i < propCount; i++) {\n const prop = expectKeyOrIndex(str);\n str = str.substr(prop[1]);\n totalOffset += prop[1];\n const value = expectType(str, cache);\n str = str.substr(value[1]);\n totalOffset += value[1];\n obj[String(prop[0])] = value[0];\n }\n // strict parsing, expect } after object literal\n if (str.charAt(0) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [obj, totalOffset + 1]; // skip final }\n}\nfunction expectClass(_str, _cache) {\n // can't be well supported, because requires calling eval (or similar)\n // in order to call serialized constructor name\n // which is unsafe\n // or assume that constructor is defined in global scope\n // but this is too much limiting\n throw new Error('Not yet implemented');\n}\nfunction expectReference(str, cache) {\n const reRef = /^[rR]:([1-9]\\d*);/;\n const [match, refIndex] = reRef.exec(str) || [];\n if (!match || !refIndex) {\n throw new SyntaxError('Expected reference value');\n }\n return [cache.get(parseInt(refIndex, 10) - 1), match.length];\n}\nfunction expectArray(str, cache) {\n const reArrayLength = /^a:(\\d+):{/;\n const [arrayLiteralBeginMatch, arrayLengthMatch] = reArrayLength.exec(str) || [];\n if (!arrayLiteralBeginMatch || !arrayLengthMatch) {\n throw new SyntaxError('Expected array length annotation');\n }\n str = str.substr(arrayLiteralBeginMatch.length);\n const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache);\n // strict parsing, expect closing } brace after array literal\n if (str.charAt(array[1]) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [array[0], arrayLiteralBeginMatch.length + array[1] + 1]; // jump over }\n}\nfunction expectArrayItems(str, expectedItems = 0, cache) {\n let key;\n let item;\n let totalOffset = 0;\n let hasContinousIndexes = true;\n let lastIndex = -1;\n const items = {};\n cache([items]);\n for (let i = 0; i < expectedItems; i++) {\n key = expectKeyOrIndex(str);\n hasContinousIndexes = hasContinousIndexes && typeof key[0] === 'number' && key[0] === lastIndex + 1;\n lastIndex = typeof key[0] === 'number' ? key[0] : lastIndex;\n str = str.substr(key[1]);\n totalOffset += key[1];\n // references are resolved immediately, so if duplicate key overwrites previous array index\n // the old value is anyway resolved\n // fixme: but next time the same reference should point to the new value\n item = expectType(str, cache);\n str = str.substr(item[1]);\n totalOffset += item[1];\n items[String(key[0])] = item[0];\n }\n if (hasContinousIndexes) {\n return [Object.values(items), totalOffset];\n }\n return [items, totalOffset];\n}\n// errorMode: 'throw', 'log', 'silent'\nfunction unserialize(str, errorMode = 'log') {\n // discuss at: https://locutus.io/php/unserialize/\n // original by: Arpad Ray (mailto:arpad@php.net)\n // improved by: Pedro Tainha (https://www.pedrotainha.com)\n // improved by: Kevin van Zonneveld (https://kvz.io)\n // improved by: Kevin van Zonneveld (https://kvz.io)\n // improved by: Chris\n // improved by: James\n // improved by: Le Torbi\n // improved by: Eli Skeggs\n // bugfixed by: dptr1988\n // bugfixed by: Kevin van Zonneveld (https://kvz.io)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: philippsimon (https://github.com/philippsimon/)\n // revised by: d3x\n // input by: Brett Zamir (https://brett-zamir.me)\n // input by: Martin (https://www.erlenwiese.de/)\n // input by: kilops\n // input by: Jaroslaw Czarniak\n // input by: lovasoa (https://github.com/lovasoa/)\n // improved by: Rafał Kukawski\n // reimplemented by: Rafał Kukawski\n // note 1: We feel the main purpose of this function should be\n // note 1: to ease the transport of data between php & js\n // note 1: Aiming for PHP-compatibility, we have to translate objects to arrays\n // example 1: unserialize('a:3:{i:0;s:5:\"Kevin\";i:1;s:3:\"van\";i:2;s:9:\"Zonneveld\";}')\n // returns 1: ['Kevin', 'van', 'Zonneveld']\n // example 2: unserialize('a:2:{s:9:\"firstName\";s:5:\"Kevin\";s:7:\"midName\";s:3:\"van\";}')\n // returns 2: {firstName: 'Kevin', midName: 'van'}\n // example 3: unserialize('a:3:{s:2:\"ü\";s:2:\"ü\";s:3:\"四\";s:3:\"四\";s:4:\"𠜎\";s:4:\"𠜎\";}')\n // returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}\n // example 4: unserialize(undefined)\n // returns 4: false\n // example 5: unserialize('O:8:\"stdClass\":1:{s:3:\"foo\";b:1;}')\n // returns 5: { foo: true }\n // example 6: unserialize('a:2:{i:0;N;i:1;s:0:\"\";}')\n // returns 6: [null, \"\"]\n // example 7: unserialize('S:7:\"\\\\65\\\\73\\\\63\\\\61\\\\70\\\\65\\\\64\";')\n // returns 7: 'escaped'\n try {\n if (typeof str !== 'string') {\n return false;\n }\n return expectType(str, initCache())[0];\n }\n catch (err) {\n if (errorMode === 'throw') {\n throw err;\n }\n else if (errorMode === 'log') {\n console.error(err);\n }\n // if silent mode we do nothing\n return false;\n }\n}" -const __locutus_standalone_js_code = "// php/var/unserialize (target function module)\nfunction initCache() {\n const store = [];\n // cache only first element, second is length to jump ahead for the parser\n const cacheBase = function cache(value) {\n store.push(value[0]);\n return value;\n };\n const cache = Object.assign(cacheBase, {\n get: (index) => {\n if (index >= store.length) {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n const cachedValue = store[index];\n if (typeof cachedValue === 'undefined') {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n return cachedValue;\n },\n });\n return cache;\n}\nfunction expectType(str, cache) {\n const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g;\n const type = (types.exec(str) || [])[0];\n if (!type) {\n throw new SyntaxError('Invalid input: ' + str);\n }\n switch (type) {\n case 'N':\n return cache([null, 2]);\n case 'b':\n return cache(expectBool(str));\n case 'i':\n return cache(expectInt(str));\n case 'd':\n return cache(expectFloat(str));\n case 's':\n return cache(expectString(str));\n case 'S':\n return cache(expectEscapedString(str));\n case 'a':\n return expectArray(str, cache);\n case 'O':\n return expectObject(str, cache);\n case 'C':\n return expectClass(str, cache);\n case 'r':\n case 'R':\n return expectReference(str, cache);\n default:\n throw new SyntaxError(`Invalid or unsupported data type: ${type}`);\n }\n}\nfunction expectBool(str) {\n const reBool = /^b:([01]);/;\n const [match, boolMatch] = reBool.exec(str) || [];\n if (!match || !boolMatch) {\n throw new SyntaxError('Invalid bool value, expected 0 or 1');\n }\n return [boolMatch === '1', match.length];\n}\nfunction expectInt(str) {\n const reInt = /^i:([+-]?\\d+);/;\n const [match, intMatch] = reInt.exec(str) || [];\n if (!match || !intMatch) {\n throw new SyntaxError('Expected an integer value');\n }\n return [parseInt(intMatch, 10), match.length];\n}\nfunction expectFloat(str) {\n const reFloat = /^d:(NAN|-?INF|(?:\\d+\\.\\d*|\\d*\\.\\d+|\\d+)(?:[eE][+-]\\d+)?);/;\n const [match, floatMatch] = reFloat.exec(str) || [];\n if (!match || !floatMatch) {\n throw new SyntaxError('Expected a float value');\n }\n let floatValue = 0;\n switch (floatMatch) {\n case 'NAN':\n floatValue = Number.NaN;\n break;\n case '-INF':\n floatValue = Number.NEGATIVE_INFINITY;\n break;\n case 'INF':\n floatValue = Number.POSITIVE_INFINITY;\n break;\n default:\n floatValue = parseFloat(floatMatch);\n break;\n }\n return [floatValue, match.length];\n}\nfunction readBytes(str, len, escapedString = false) {\n let bytes = 0;\n let out = '';\n let c = 0;\n const strLen = str.length;\n let wasHighSurrogate = false;\n let escapedChars = 0;\n while (bytes < len && c < strLen) {\n let chr = str.charAt(c);\n const code = chr.charCodeAt(0);\n const isHighSurrogate = code >= 0xd800 && code <= 0xdbff;\n const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff;\n if (escapedString && chr === '\\\\') {\n chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16));\n escapedChars++;\n // each escaped sequence is 3 characters. Go 2 chars ahead.\n // third character will be jumped over a few lines later\n c += 2;\n }\n c++;\n bytes +=\n isHighSurrogate || (isLowSurrogate && wasHighSurrogate)\n ? // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate\n // if low surrogate preceded by high surrogate, add 2 bytes\n 2\n : code > 0x7ff\n ? // otherwise low surrogate falls into this part\n 3\n : code > 0x7f\n ? 2\n : 1;\n // if high surrogate is not followed by low surrogate, add 1 more byte\n bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0;\n out += chr;\n wasHighSurrogate = isHighSurrogate;\n }\n return [out, bytes, escapedChars];\n}\nfunction expectString(str) {\n // PHP strings consist of one-byte characters.\n // JS uses 2 bytes with possible surrogate pairs.\n // Serialized length of 2 is still 1 JS string character\n const reStrLength = /^s:(\\d+):\"/g; // also match the opening \" char\n const [match, byteLenMatch] = reStrLength.exec(str) || [];\n if (!match || !byteLenMatch) {\n throw new SyntaxError('Expected a string value');\n }\n const len = parseInt(byteLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes] = readBytes(str, len);\n if (bytes !== len) {\n throw new SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectEscapedString(str) {\n const reStrLength = /^S:(\\d+):\"/g; // also match the opening \" char\n const [match, strLenMatch] = reStrLength.exec(str) || [];\n if (!match || !strLenMatch) {\n throw new SyntaxError('Expected an escaped string value');\n }\n const len = parseInt(strLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes, escapedChars] = readBytes(str, len, true);\n if (bytes !== len) {\n throw new SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length + escapedChars * 2);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectKeyOrIndex(str) {\n try {\n return expectString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectEscapedString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectInt(str);\n }\n catch (_err) {\n throw new SyntaxError('Expected key or index');\n }\n}\nfunction expectObject(str, cache) {\n // O:<class name length>:\"class name\":<prop count>:{<props and values>}\n // O:8:\"stdClass\":2:{s:3:\"foo\";s:3:\"bar\";s:3:\"bar\";s:3:\"baz\";}\n const reObjectLiteral = /^O:(\\d+):\"([^\"]+)\":(\\d+):\\{/;\n const [objectLiteralBeginMatch /* classNameLengthMatch */, , className, propCountMatch] = reObjectLiteral.exec(str) || [];\n if (!objectLiteralBeginMatch || !propCountMatch) {\n throw new SyntaxError('Invalid input');\n }\n if (className !== 'stdClass') {\n throw new SyntaxError(`Unsupported object type: ${className}`);\n }\n let totalOffset = objectLiteralBeginMatch.length;\n const propCount = parseInt(propCountMatch, 10);\n const obj = {};\n cache([obj]);\n str = str.substr(totalOffset);\n for (let i = 0; i < propCount; i++) {\n const prop = expectKeyOrIndex(str);\n str = str.substr(prop[1]);\n totalOffset += prop[1];\n const value = expectType(str, cache);\n str = str.substr(value[1]);\n totalOffset += value[1];\n obj[String(prop[0])] = value[0];\n }\n // strict parsing, expect } after object literal\n if (str.charAt(0) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [obj, totalOffset + 1]; // skip final }\n}\nfunction expectClass(_str, _cache) {\n // can't be well supported, because requires calling eval (or similar)\n // in order to call serialized constructor name\n // which is unsafe\n // or assume that constructor is defined in global scope\n // but this is too much limiting\n throw new Error('Not yet implemented');\n}\nfunction expectReference(str, cache) {\n const reRef = /^[rR]:([1-9]\\d*);/;\n const [match, refIndex] = reRef.exec(str) || [];\n if (!match || !refIndex) {\n throw new SyntaxError('Expected reference value');\n }\n return [cache.get(parseInt(refIndex, 10) - 1), match.length];\n}\nfunction expectArray(str, cache) {\n const reArrayLength = /^a:(\\d+):{/;\n const [arrayLiteralBeginMatch, arrayLengthMatch] = reArrayLength.exec(str) || [];\n if (!arrayLiteralBeginMatch || !arrayLengthMatch) {\n throw new SyntaxError('Expected array length annotation');\n }\n str = str.substr(arrayLiteralBeginMatch.length);\n const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache);\n // strict parsing, expect closing } brace after array literal\n if (str.charAt(array[1]) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [array[0], arrayLiteralBeginMatch.length + array[1] + 1]; // jump over }\n}\nfunction expectArrayItems(str, expectedItems = 0, cache) {\n let key;\n let item;\n let totalOffset = 0;\n let hasContinousIndexes = true;\n let lastIndex = -1;\n const items = {};\n cache([items]);\n for (let i = 0; i < expectedItems; i++) {\n key = expectKeyOrIndex(str);\n hasContinousIndexes = hasContinousIndexes && typeof key[0] === 'number' && key[0] === lastIndex + 1;\n lastIndex = typeof key[0] === 'number' ? key[0] : lastIndex;\n str = str.substr(key[1]);\n totalOffset += key[1];\n // references are resolved immediately, so if duplicate key overwrites previous array index\n // the old value is anyway resolved\n // fixme: but next time the same reference should point to the new value\n item = expectType(str, cache);\n str = str.substr(item[1]);\n totalOffset += item[1];\n items[String(key[0])] = item[0];\n }\n if (hasContinousIndexes) {\n return [Object.values(items), totalOffset];\n }\n return [items, totalOffset];\n}\n// errorMode: 'throw', 'log', 'silent'\nfunction unserialize(str, errorMode = 'log') {\n // discuss at: https://locutus.io/php/unserialize/\n // original by: Arpad Ray (mailto:arpad@php.net)\n // improved by: Pedro Tainha (https://www.pedrotainha.com)\n // improved by: Kevin van Zonneveld (https://kvz.io)\n // improved by: Kevin van Zonneveld (https://kvz.io)\n // improved by: Chris\n // improved by: James\n // improved by: Le Torbi\n // improved by: Eli Skeggs\n // bugfixed by: dptr1988\n // bugfixed by: Kevin van Zonneveld (https://kvz.io)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: philippsimon (https://github.com/philippsimon/)\n // revised by: d3x\n // input by: Brett Zamir (https://brett-zamir.me)\n // input by: Martin (https://www.erlenwiese.de/)\n // input by: kilops\n // input by: Jaroslaw Czarniak\n // input by: lovasoa (https://github.com/lovasoa/)\n // improved by: Rafał Kukawski\n // reimplemented by: Rafał Kukawski\n // note 1: We feel the main purpose of this function should be\n // note 1: to ease the transport of data between php & js\n // note 1: Aiming for PHP-compatibility, we have to translate objects to arrays\n // example 1: unserialize('a:3:{i:0;s:5:\"Kevin\";i:1;s:3:\"van\";i:2;s:9:\"Zonneveld\";}')\n // returns 1: ['Kevin', 'van', 'Zonneveld']\n // example 2: unserialize('a:2:{s:9:\"firstName\";s:5:\"Kevin\";s:7:\"midName\";s:3:\"van\";}')\n // returns 2: {firstName: 'Kevin', midName: 'van'}\n // example 3: unserialize('a:3:{s:2:\"ü\";s:2:\"ü\";s:3:\"四\";s:3:\"四\";s:4:\"𠜎\";s:4:\"𠜎\";}')\n // returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}\n // example 4: unserialize(undefined)\n // returns 4: false\n // example 5: unserialize('O:8:\"stdClass\":1:{s:3:\"foo\";b:1;}')\n // returns 5: { foo: true }\n // example 6: unserialize('a:2:{i:0;N;i:1;s:0:\"\";}')\n // returns 6: [null, \"\"]\n // example 7: unserialize('S:7:\"\\\\65\\\\73\\\\63\\\\61\\\\70\\\\65\\\\64\";')\n // returns 7: 'escaped'\n try {\n if (typeof str !== 'string') {\n return false;\n }\n return expectType(str, initCache())[0];\n }\n catch (err) {\n if (errorMode === 'throw') {\n throw err;\n }\n else if (errorMode === 'log') {\n console.error(err);\n }\n // if silent mode we do nothing\n return false;\n }\n}" +const __locutus_module_js_code = "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.unserialize = unserialize;\nconst DANGEROUS_UNSERIALIZE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\nfunction setUnserializedProperty(target, key, value) {\n if (DANGEROUS_UNSERIALIZE_KEYS.has(key)) {\n Object.defineProperty(target, key, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n return;\n }\n target[key] = value;\n}\nfunction initCache() {\n const store = [];\n // cache only first element, second is length to jump ahead for the parser\n const cacheBase = function cache(value) {\n store.push(value[0]);\n return value;\n };\n const cache = Object.assign(cacheBase, {\n get: (index) => {\n if (index >= store.length) {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n const cachedValue = store[index];\n if (typeof cachedValue === 'undefined') {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n return cachedValue;\n },\n });\n return cache;\n}\nfunction expectType(str, cache) {\n const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g;\n const type = (types.exec(str) || [])[0];\n if (!type) {\n throw new SyntaxError('Invalid input: ' + str);\n }\n switch (type) {\n case 'N':\n return cache([null, 2]);\n case 'b':\n return cache(expectBool(str));\n case 'i':\n return cache(expectInt(str));\n case 'd':\n return cache(expectFloat(str));\n case 's':\n return cache(expectString(str));\n case 'S':\n return cache(expectEscapedString(str));\n case 'a':\n return expectArray(str, cache);\n case 'O':\n return expectObject(str, cache);\n case 'C':\n return expectClass(str, cache);\n case 'r':\n case 'R':\n return expectReference(str, cache);\n default:\n throw new SyntaxError(`Invalid or unsupported data type: ${type}`);\n }\n}\nfunction expectBool(str) {\n const reBool = /^b:([01]);/;\n const [match, boolMatch] = reBool.exec(str) || [];\n if (!match || !boolMatch) {\n throw new SyntaxError('Invalid bool value, expected 0 or 1');\n }\n return [boolMatch === '1', match.length];\n}\nfunction expectInt(str) {\n const reInt = /^i:([+-]?\\d+);/;\n const [match, intMatch] = reInt.exec(str) || [];\n if (!match || !intMatch) {\n throw new SyntaxError('Expected an integer value');\n }\n return [parseInt(intMatch, 10), match.length];\n}\nfunction expectFloat(str) {\n const reFloat = /^d:(NAN|-?INF|(?:\\d+\\.\\d*|\\d*\\.\\d+|\\d+)(?:[eE][+-]\\d+)?);/;\n const [match, floatMatch] = reFloat.exec(str) || [];\n if (!match || !floatMatch) {\n throw new SyntaxError('Expected a float value');\n }\n let floatValue = 0;\n switch (floatMatch) {\n case 'NAN':\n floatValue = Number.NaN;\n break;\n case '-INF':\n floatValue = Number.NEGATIVE_INFINITY;\n break;\n case 'INF':\n floatValue = Number.POSITIVE_INFINITY;\n break;\n default:\n floatValue = parseFloat(floatMatch);\n break;\n }\n return [floatValue, match.length];\n}\nfunction readBytes(str, len, escapedString = false) {\n let bytes = 0;\n let out = '';\n let c = 0;\n const strLen = str.length;\n let wasHighSurrogate = false;\n let escapedChars = 0;\n while (bytes < len && c < strLen) {\n let chr = str.charAt(c);\n const code = chr.charCodeAt(0);\n const isHighSurrogate = code >= 0xd800 && code <= 0xdbff;\n const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff;\n if (escapedString && chr === '\\\\') {\n chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16));\n escapedChars++;\n // each escaped sequence is 3 characters. Go 2 chars ahead.\n // third character will be jumped over a few lines later\n c += 2;\n }\n c++;\n bytes +=\n isHighSurrogate || (isLowSurrogate && wasHighSurrogate)\n ? // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate\n // if low surrogate preceded by high surrogate, add 2 bytes\n 2\n : code > 0x7ff\n ? // otherwise low surrogate falls into this part\n 3\n : code > 0x7f\n ? 2\n : 1;\n // if high surrogate is not followed by low surrogate, add 1 more byte\n bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0;\n out += chr;\n wasHighSurrogate = isHighSurrogate;\n }\n return [out, bytes, escapedChars];\n}\nfunction expectString(str) {\n // PHP strings consist of one-byte characters.\n // JS uses 2 bytes with possible surrogate pairs.\n // Serialized length of 2 is still 1 JS string character\n const reStrLength = /^s:(\\d+):\"/g; // also match the opening \" char\n const [match, byteLenMatch] = reStrLength.exec(str) || [];\n if (!match || !byteLenMatch) {\n throw new SyntaxError('Expected a string value');\n }\n const len = parseInt(byteLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes] = readBytes(str, len);\n if (bytes !== len) {\n throw new SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectEscapedString(str) {\n const reStrLength = /^S:(\\d+):\"/g; // also match the opening \" char\n const [match, strLenMatch] = reStrLength.exec(str) || [];\n if (!match || !strLenMatch) {\n throw new SyntaxError('Expected an escaped string value');\n }\n const len = parseInt(strLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes, escapedChars] = readBytes(str, len, true);\n if (bytes !== len) {\n throw new SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length + escapedChars * 2);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectKeyOrIndex(str) {\n try {\n return expectString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectEscapedString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectInt(str);\n }\n catch (_err) {\n throw new SyntaxError('Expected key or index');\n }\n}\nfunction expectObject(str, cache) {\n // O:<class name length>:\"class name\":<prop count>:{<props and values>}\n // O:8:\"stdClass\":2:{s:3:\"foo\";s:3:\"bar\";s:3:\"bar\";s:3:\"baz\";}\n const reObjectLiteral = /^O:(\\d+):\"([^\"]+)\":(\\d+):\\{/;\n const [objectLiteralBeginMatch /* classNameLengthMatch */, , className, propCountMatch] = reObjectLiteral.exec(str) || [];\n if (!objectLiteralBeginMatch || !propCountMatch) {\n throw new SyntaxError('Invalid input');\n }\n if (className !== 'stdClass') {\n throw new SyntaxError(`Unsupported object type: ${className}`);\n }\n let totalOffset = objectLiteralBeginMatch.length;\n const propCount = parseInt(propCountMatch, 10);\n const obj = {};\n cache([obj]);\n str = str.substr(totalOffset);\n for (let i = 0; i < propCount; i++) {\n const prop = expectKeyOrIndex(str);\n str = str.substr(prop[1]);\n totalOffset += prop[1];\n const value = expectType(str, cache);\n str = str.substr(value[1]);\n totalOffset += value[1];\n setUnserializedProperty(obj, String(prop[0]), value[0]);\n }\n // strict parsing, expect } after object literal\n if (str.charAt(0) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [obj, totalOffset + 1]; // skip final }\n}\nfunction expectClass(_str, _cache) {\n // can't be well supported, because requires calling eval (or similar)\n // in order to call serialized constructor name\n // which is unsafe\n // or assume that constructor is defined in global scope\n // but this is too much limiting\n throw new Error('Not yet implemented');\n}\nfunction expectReference(str, cache) {\n const reRef = /^[rR]:([1-9]\\d*);/;\n const [match, refIndex] = reRef.exec(str) || [];\n if (!match || !refIndex) {\n throw new SyntaxError('Expected reference value');\n }\n return [cache.get(parseInt(refIndex, 10) - 1), match.length];\n}\nfunction expectArray(str, cache) {\n const reArrayLength = /^a:(\\d+):{/;\n const [arrayLiteralBeginMatch, arrayLengthMatch] = reArrayLength.exec(str) || [];\n if (!arrayLiteralBeginMatch || !arrayLengthMatch) {\n throw new SyntaxError('Expected array length annotation');\n }\n str = str.substr(arrayLiteralBeginMatch.length);\n const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache);\n // strict parsing, expect closing } brace after array literal\n if (str.charAt(array[1]) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [array[0], arrayLiteralBeginMatch.length + array[1] + 1]; // jump over }\n}\nfunction expectArrayItems(str, expectedItems = 0, cache) {\n let key;\n let item;\n let totalOffset = 0;\n let hasContinousIndexes = true;\n let lastIndex = -1;\n const items = {};\n cache([items]);\n for (let i = 0; i < expectedItems; i++) {\n key = expectKeyOrIndex(str);\n hasContinousIndexes = hasContinousIndexes && typeof key[0] === 'number' && key[0] === lastIndex + 1;\n lastIndex = typeof key[0] === 'number' ? key[0] : lastIndex;\n str = str.substr(key[1]);\n totalOffset += key[1];\n // references are resolved immediately, so if duplicate key overwrites previous array index\n // the old value is anyway resolved\n // fixme: but next time the same reference should point to the new value\n item = expectType(str, cache);\n str = str.substr(item[1]);\n totalOffset += item[1];\n setUnserializedProperty(items, String(key[0]), item[0]);\n }\n if (hasContinousIndexes) {\n return [Object.values(items), totalOffset];\n }\n return [items, totalOffset];\n}\n// errorMode: 'throw', 'log', 'silent'\nfunction unserialize(str, errorMode = 'log') {\n // discuss at: https://locutus.io/php/unserialize/\n // original by: Arpad Ray (mailto:arpad@php.net)\n // improved by: Pedro Tainha (https://www.pedrotainha.com)\n // improved by: Kevin van Zonneveld (https://kvz.io)\n // improved by: Kevin van Zonneveld (https://kvz.io)\n // improved by: Chris\n // improved by: James\n // improved by: Le Torbi\n // improved by: Eli Skeggs\n // bugfixed by: dptr1988\n // bugfixed by: Kevin van Zonneveld (https://kvz.io)\n // bugfixed by: Brett Zamir (https://brett-zamir.me)\n // bugfixed by: philippsimon (https://github.com/philippsimon/)\n // revised by: d3x\n // input by: Brett Zamir (https://brett-zamir.me)\n // input by: Martin (https://www.erlenwiese.de/)\n // input by: kilops\n // input by: Jaroslaw Czarniak\n // input by: lovasoa (https://github.com/lovasoa/)\n // improved by: Rafał Kukawski\n // reimplemented by: Rafał Kukawski\n // note 1: We feel the main purpose of this function should be\n // note 1: to ease the transport of data between php & js\n // note 1: Aiming for PHP-compatibility, we have to translate objects to arrays\n // example 1: unserialize('a:3:{i:0;s:5:\"Kevin\";i:1;s:3:\"van\";i:2;s:9:\"Zonneveld\";}')\n // returns 1: ['Kevin', 'van', 'Zonneveld']\n // example 2: unserialize('a:2:{s:9:\"firstName\";s:5:\"Kevin\";s:7:\"midName\";s:3:\"van\";}')\n // returns 2: {firstName: 'Kevin', midName: 'van'}\n // example 3: unserialize('a:3:{s:2:\"ü\";s:2:\"ü\";s:3:\"四\";s:3:\"四\";s:4:\"𠜎\";s:4:\"𠜎\";}')\n // returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}\n // example 4: unserialize(undefined)\n // returns 4: false\n // example 5: unserialize('O:8:\"stdClass\":1:{s:3:\"foo\";b:1;}')\n // returns 5: { foo: true }\n // example 6: unserialize('a:2:{i:0;N;i:1;s:0:\"\";}')\n // returns 6: [null, \"\"]\n // example 7: unserialize('S:7:\"\\\\65\\\\73\\\\63\\\\61\\\\70\\\\65\\\\64\";')\n // returns 7: 'escaped'\n try {\n if (typeof str !== 'string') {\n return false;\n }\n return expectType(str, initCache())[0];\n }\n catch (err) {\n if (errorMode === 'throw') {\n throw err;\n }\n else if (errorMode === 'log') {\n console.error(err);\n }\n // if silent mode we do nothing\n return false;\n }\n}" +const __locutus_standalone_ts_code = "const DANGEROUS_UNSERIALIZE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\nfunction setUnserializedProperty(target, key, value) {\n if (DANGEROUS_UNSERIALIZE_KEYS.has(key)) {\n Object.defineProperty(target, key, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n return;\n }\n target[key] = value;\n}\nfunction initCache() {\n const store = [];\n // cache only first element, second is length to jump ahead for the parser\n const cacheBase = function cache(value) {\n store.push(value[0]);\n return value;\n };\n const cache = Object.assign(cacheBase, {\n get: (index) => {\n if (index >= store.length) {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n const cachedValue = store[index];\n if (typeof cachedValue === 'undefined') {\n throw new RangeError(`Can't resolve reference ${index + 1}`);\n }\n return cachedValue;\n },\n });\n return cache;\n}\nfunction expectType(str, cache) {\n const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g;\n const type = (types.exec(str) || [])[0];\n if (!type) {\n throw new SyntaxError('Invalid input: ' + str);\n }\n switch (type) {\n case 'N':\n return cache([null, 2]);\n case 'b':\n return cache(expectBool(str));\n case 'i':\n return cache(expectInt(str));\n case 'd':\n return cache(expectFloat(str));\n case 's':\n return cache(expectString(str));\n case 'S':\n return cache(expectEscapedString(str));\n case 'a':\n return expectArray(str, cache);\n case 'O':\n return expectObject(str, cache);\n case 'C':\n return expectClass(str, cache);\n case 'r':\n case 'R':\n return expectReference(str, cache);\n default:\n throw new SyntaxError(`Invalid or unsupported data type: ${type}`);\n }\n}\nfunction expectBool(str) {\n const reBool = /^b:([01]);/;\n const [match, boolMatch] = reBool.exec(str) || [];\n if (!match || !boolMatch) {\n throw new SyntaxError('Invalid bool value, expected 0 or 1');\n }\n return [boolMatch === '1', match.length];\n}\nfunction expectInt(str) {\n const reInt = /^i:([+-]?\\d+);/;\n const [match, intMatch] = reInt.exec(str) || [];\n if (!match || !intMatch) {\n throw new SyntaxError('Expected an integer value');\n }\n return [parseInt(intMatch, 10), match.length];\n}\nfunction expectFloat(str) {\n const reFloat = /^d:(NAN|-?INF|(?:\\d+\\.\\d*|\\d*\\.\\d+|\\d+)(?:[eE][+-]\\d+)?);/;\n const [match, floatMatch] = reFloat.exec(str) || [];\n if (!match || !floatMatch) {\n throw new SyntaxError('Expected a float value');\n }\n let floatValue = 0;\n switch (floatMatch) {\n case 'NAN':\n floatValue = Number.NaN;\n break;\n case '-INF':\n floatValue = Number.NEGATIVE_INFINITY;\n break;\n case 'INF':\n floatValue = Number.POSITIVE_INFINITY;\n break;\n default:\n floatValue = parseFloat(floatMatch);\n break;\n }\n return [floatValue, match.length];\n}\nfunction readBytes(str, len, escapedString = false) {\n let bytes = 0;\n let out = '';\n let c = 0;\n const strLen = str.length;\n let wasHighSurrogate = false;\n let escapedChars = 0;\n while (bytes < len && c < strLen) {\n let chr = str.charAt(c);\n const code = chr.charCodeAt(0);\n const isHighSurrogate = code >= 0xd800 && code <= 0xdbff;\n const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff;\n if (escapedString && chr === '\\\\') {\n chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16));\n escapedChars++;\n // each escaped sequence is 3 characters. Go 2 chars ahead.\n // third character will be jumped over a few lines later\n c += 2;\n }\n c++;\n bytes +=\n isHighSurrogate || (isLowSurrogate && wasHighSurrogate)\n ? // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate\n // if low surrogate preceded by high surrogate, add 2 bytes\n 2\n : code > 0x7ff\n ? // otherwise low surrogate falls into this part\n 3\n : code > 0x7f\n ? 2\n : 1;\n // if high surrogate is not followed by low surrogate, add 1 more byte\n bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0;\n out += chr;\n wasHighSurrogate = isHighSurrogate;\n }\n return [out, bytes, escapedChars];\n}\nfunction expectString(str) {\n // PHP strings consist of one-byte characters.\n // JS uses 2 bytes with possible surrogate pairs.\n // Serialized length of 2 is still 1 JS string character\n const reStrLength = /^s:(\\d+):\"/g; // also match the opening \" char\n const [match, byteLenMatch] = reStrLength.exec(str) || [];\n if (!match || !byteLenMatch) {\n throw new SyntaxError('Expected a string value');\n }\n const len = parseInt(byteLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes] = readBytes(str, len);\n if (bytes !== len) {\n throw new SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectEscapedString(str) {\n const reStrLength = /^S:(\\d+):\"/g; // also match the opening \" char\n const [match, strLenMatch] = reStrLength.exec(str) || [];\n if (!match || !strLenMatch) {\n throw new SyntaxError('Expected an escaped string value');\n }\n const len = parseInt(strLenMatch, 10);\n str = str.substr(match.length);\n const [strMatch, bytes, escapedChars] = readBytes(str, len, true);\n if (bytes !== len) {\n throw new SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`);\n }\n str = str.substr(strMatch.length + escapedChars * 2);\n // strict parsing, match closing \"; chars\n if (!str.startsWith('\";')) {\n throw new SyntaxError('Expected \";');\n }\n return [strMatch, match.length + strMatch.length + 2]; // skip last \";\n}\nfunction expectKeyOrIndex(str) {\n try {\n return expectString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectEscapedString(str);\n // biome-ignore lint/suspicious/noEmptyBlockStatements: fallthrough to next parser\n }\n catch (_err) { }\n try {\n return expectInt(str);\n }\n catch (_err) {\n throw new SyntaxError('Expected key or index');\n }\n}\nfunction expectObject(str, cache) {\n // O:<class name length>:\"class name\":<prop count>:{<props and values>}\n // O:8:\"stdClass\":2:{s:3:\"foo\";s:3:\"bar\";s:3:\"bar\";s:3:\"baz\";}\n const reObjectLiteral = /^O:(\\d+):\"([^\"]+)\":(\\d+):\\{/;\n const [objectLiteralBeginMatch /* classNameLengthMatch */, , className, propCountMatch] = reObjectLiteral.exec(str) || [];\n if (!objectLiteralBeginMatch || !propCountMatch) {\n throw new SyntaxError('Invalid input');\n }\n if (className !== 'stdClass') {\n throw new SyntaxError(`Unsupported object type: ${className}`);\n }\n let totalOffset = objectLiteralBeginMatch.length;\n const propCount = parseInt(propCountMatch, 10);\n const obj = {};\n cache([obj]);\n str = str.substr(totalOffset);\n for (let i = 0; i < propCount; i++) {\n const prop = expectKeyOrIndex(str);\n str = str.substr(prop[1]);\n totalOffset += prop[1];\n const value = expectType(str, cache);\n str = str.substr(value[1]);\n totalOffset += value[1];\n setUnserializedProperty(obj, String(prop[0]), value[0]);\n }\n // strict parsing, expect } after object literal\n if (str.charAt(0) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [obj, totalOffset + 1]; // skip final }\n}\nfunction expectClass(_str, _cache) {\n // can't be well supported, because requires calling eval (or similar)\n // in order to call serialized constructor name\n // which is unsafe\n // or assume that constructor is defined in global scope\n // but this is too much limiting\n throw new Error('Not yet implemented');\n}\nfunction expectReference(str, cache) {\n const reRef = /^[rR]:([1-9]\\d*);/;\n const [match, refIndex] = reRef.exec(str) || [];\n if (!match || !refIndex) {\n throw new SyntaxError('Expected reference value');\n }\n return [cache.get(parseInt(refIndex, 10) - 1), match.length];\n}\nfunction expectArray(str, cache) {\n const reArrayLength = /^a:(\\d+):{/;\n const [arrayLiteralBeginMatch, arrayLengthMatch] = reArrayLength.exec(str) || [];\n if (!arrayLiteralBeginMatch || !arrayLengthMatch) {\n throw new SyntaxError('Expected array length annotation');\n }\n str = str.substr(arrayLiteralBeginMatch.length);\n const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache);\n // strict parsing, expect closing } brace after array literal\n if (str.charAt(array[1]) !== '}') {\n throw new SyntaxError('Expected }');\n }\n return [array[0], arrayLiteralBeginMatch.length + array[1] + 1]; // jump over }\n}\nfunction expectArrayItems(str, expectedItems = 0, cache) {\n let key;\n let item;\n let totalOffset = 0;\n let hasContinousIndexes = true;\n let lastIndex = -1;\n const items = {};\n cache([items]);\n for (let i = 0; i < expectedItems; i++) {\n key = expectKeyOrIndex(str);\n hasContinousIndexes = hasContinousIndexes && typeof key[0] === 'number' && key[0] === lastIndex + 1;\n lastIndex = typeof key[0] === 'number' ? key[0] : lastIndex;\n str = str.substr(key[1]);\n totalOffset += key[1];\n // references are resolved immediately, so if duplicate key overwrites previous array index\n // the old value is anyway resolved\n // fixme: but next time the same reference should point to the new value\n item = expectType(str, cache) ... [truncated]
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/locutusjs/locutus/commit/345a6211e1e6f939f96a7090bfeff642c9fcf9e4nvdPatchWEB
- github.com/locutusjs/locutus/security/advisories/GHSA-4mph-v827-f877nvdExploitMitigationVendor AdvisoryWEB
- github.com/advisories/GHSA-4mph-v827-f877ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-33993ghsaADVISORY
- github.com/locutusjs/locutus/pull/597nvdIssue TrackingWEB
- github.com/locutusjs/locutus/releases/tag/v3.0.25nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.