VYPR
Critical severity9.8NVD Advisory· Published Mar 27, 2026· Updated Apr 1, 2026

CVE-2026-33994

CVE-2026-33994

Description

Locutus brings stdlibs of other programming languages to JavaScript for educational purposes. Starting in version 2.0.39 and prior to version 3.0.25, a prototype pollution vulnerability exists in the parse_str function of the npm package locutus. An attacker can pollute Object.prototype by overriding RegExp.prototype.test and then passing a crafted query string to parse_str, bypassing the prototype pollution guard. This vulnerability stems from an incomplete fix for CVE-2026-25521. The CVE-2026-25521 patch replaced the String.prototype.includes()-based guard with a RegExp.prototype.test()-based guard. However, RegExp.prototype.test is itself a writable prototype method that can be overridden, making the new guard bypassable in the same way as the original — trading one hijackable built-in for another. Version 3.0.25 contains an updated fix.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
locutusnpm
>= 2.0.39, < 3.0.253.0.25

Affected products

1
  • cpe:2.3:a:locutus:locutus:*:*:*:*:*:node.js:*:*
    Range: >=2.0.39,<3.0.25

Patches

1
345a6211e1e6

fix: harden php prototype pollution sinks (#597)

https://github.com/locutusjs/locutusKevin van ZonneveldMar 25, 2026via ghsa
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

7

News mentions

0

No linked articles in our index yet.