locutus
Version:
Locutus other languages' standard libraries to JavaScript for fun and educational purposes
159 lines (158 loc) • 6.48 kB
JavaScript
import { getPhpGlobalScope } from "../_helpers/_phpRuntimeState.js";
import { isPhpAssocObject } from "../_helpers/_phpTypes.js";
const DANGEROUS_PARSE_STR_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
function isDangerousParseStrKey(key) {
return DANGEROUS_PARSE_STR_KEYS.has(key);
}
export function parse_str(str, array) {
// discuss at: https://locutus.io/php/parse_str/
// original by: Cagri Ekin
// improved by: Michael White (https://getsprink.com)
// improved by: Jack
// improved by: Brett Zamir (https://brett-zamir.me)
// bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)
// bugfixed by: Brett Zamir (https://brett-zamir.me)
// bugfixed by: stag019
// bugfixed by: Brett Zamir (https://brett-zamir.me)
// bugfixed by: MIO_KODUKI (https://mio-koduki.blogspot.com/)
// reimplemented by: stag019
// input by: Dreamer
// input by: Zaide (https://zaidesthings.com/)
// input by: David Pesta (https://davidpesta.com/)
// input by: jeicquest
// bugfixed by: Rafał Kukawski
// note 1: When no argument is specified, will put variables in global scope.
// note 1: When a particular argument has been passed, and the
// note 1: returned value is different parse_str of PHP.
// note 1: For example, a=b=c&d====c
// example 1: var $arr = {}
// example 1: parse_str('first=foo&second=bar', $arr)
// example 1: var $result = $arr
// returns 1: { first: 'foo', second: 'bar' }
// example 2: var $arr = {}
// example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', $arr)
// example 2: var $result = $arr
// returns 2: { str_a: "Jack and Jill didn't see the well." }
// example 3: var $abc = {3:'a'}
// example 3: parse_str('a[b]["c"]=def&a[q]=t+5', $abc)
// example 3: var $result = $abc
// returns 3: {"3":"a","a":{"b":{"c":"def"},"q":"t 5"}}
// example 4: var $arr = {}
// example 4: parse_str('a[][]=value', $arr)
// example 4: var $result = $arr
// returns 4: {"a":{"0":{"0":"value"}}}
// example 5: var $arr = {}
// example 5: parse_str('a=1&a[]=2', $arr)
// example 5: var $result = $arr
// returns 5: {"a":{"0":"2"}}
const strArr = String(str).replace(/^&/, '').replace(/&$/, '').split('&');
const sal = strArr.length;
let i = 0;
let j = 0;
let ct = 0;
let lastObj = {};
let obj = {};
let chr;
let tmp = [];
let key = '';
let value = '';
let postLeftBracketPos = 0;
let keys = [];
let keysLen = 0;
const _fixStr = function (str) {
return decodeURIComponent(str.replace(/\+/g, '%20'));
};
const target = array || getPhpGlobalScope();
for (i = 0; i < sal; i++) {
tmp = (strArr[i] ?? '').split('=');
key = _fixStr(tmp[0] ?? '');
value = tmp.length < 2 ? '' : _fixStr(tmp[1] ?? '');
while (key.charAt(0) === ' ') {
key = key.slice(1);
}
const nullByteIndex = key.indexOf('\x00');
if (nullByteIndex > -1) {
key = key.slice(0, nullByteIndex);
}
if (key && key.charAt(0) !== '[') {
keys = [];
postLeftBracketPos = 0;
for (j = 0; j < key.length; j++) {
if (key.charAt(j) === '[' && !postLeftBracketPos) {
postLeftBracketPos = j + 1;
}
else if (key.charAt(j) === ']') {
if (postLeftBracketPos) {
if (!keys.length) {
keys.push(key.slice(0, postLeftBracketPos - 1));
}
keys.push(key.substr(postLeftBracketPos, j - postLeftBracketPos));
postLeftBracketPos = 0;
if (key.charAt(j + 1) !== '[') {
break;
}
}
}
}
if (!keys.length) {
keys = [key];
}
let primaryKey = keys[0] ?? '';
for (j = 0; j < primaryKey.length; j++) {
chr = primaryKey.charAt(j);
if (chr === ' ' || chr === '.' || chr === '[') {
primaryKey = primaryKey.substring(0, j) + '_' + primaryKey.substring(j + 1);
}
if (chr === '[') {
break;
}
}
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;
if ((key === '' || key === ' ') && j !== 0) {
// Insert new dimension
ct = -1;
for (const objKey of Object.keys(obj)) {
if (+objKey > ct && /^\d+$/.test(objKey)) {
ct = +objKey;
}
}
key = String(ct + 1);
}
if (isDangerousParseStrKey(key)) {
skipAssignment = true;
break;
}
// if primitive value, replace with object
const current = obj[key];
if (!isPhpAssocObject(current)) {
obj[key] = {};
}
const next = obj[key];
if (!isPhpAssocObject(next)) {
break;
}
obj = next;
}
if (skipAssignment || isDangerousParseStrKey(key)) {
continue;
}
lastObj[key] = value;
}
}
}