You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
127 lines
3.3 KiB
127 lines
3.3 KiB
3 years ago
|
"use strict";
|
||
|
|
||
|
const fastDecode = require("fast-decode-uri-component");
|
||
|
|
||
|
const plusRegex = /\+/g;
|
||
|
const Empty = function () {};
|
||
|
Empty.prototype = Object.create(null);
|
||
|
|
||
|
/**
|
||
|
* @callback parse
|
||
|
* @param {string} input
|
||
|
*/
|
||
|
function parse(input) {
|
||
|
// Optimization: Use new Empty() instead of Object.create(null) for performance
|
||
|
// v8 has a better optimization for initializing functions compared to Object
|
||
|
const result = new Empty();
|
||
|
|
||
|
if (typeof input !== "string") {
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
let inputLength = input.length;
|
||
|
let key = "";
|
||
|
let value = "";
|
||
|
let startingIndex = -1;
|
||
|
let equalityIndex = -1;
|
||
|
let shouldDecodeKey = false;
|
||
|
let shouldDecodeValue = false;
|
||
|
let keyHasPlus = false;
|
||
|
let valueHasPlus = false;
|
||
|
let hasBothKeyValuePair = false;
|
||
|
let c = 0;
|
||
|
|
||
|
// Have a boundary of input.length + 1 to access last pair inside the loop.
|
||
|
for (let i = 0; i < inputLength + 1; i++) {
|
||
|
c = i !== inputLength ? input.charCodeAt(i) : 38;
|
||
|
|
||
|
// Handle '&' and end of line to pass the current values to result
|
||
|
if (c === 38) {
|
||
|
hasBothKeyValuePair = equalityIndex > startingIndex;
|
||
|
|
||
|
// Optimization: Reuse equality index to store the end of key
|
||
|
if (!hasBothKeyValuePair) {
|
||
|
equalityIndex = i;
|
||
|
}
|
||
|
|
||
|
key = input.slice(startingIndex + 1, equalityIndex);
|
||
|
|
||
|
// Add key/value pair only if the range size is greater than 1; a.k.a. contains at least "="
|
||
|
if (hasBothKeyValuePair || key.length > 0) {
|
||
|
// Optimization: Replace '+' with space
|
||
|
if (keyHasPlus) {
|
||
|
key = key.replace(plusRegex, " ");
|
||
|
}
|
||
|
|
||
|
// Optimization: Do not decode if it's not necessary.
|
||
|
if (shouldDecodeKey) {
|
||
|
key = fastDecode(key) || key;
|
||
|
}
|
||
|
|
||
|
if (hasBothKeyValuePair) {
|
||
|
value = input.slice(equalityIndex + 1, i);
|
||
|
|
||
|
if (valueHasPlus) {
|
||
|
value = value.replace(plusRegex, " ");
|
||
|
}
|
||
|
|
||
|
if (shouldDecodeValue) {
|
||
|
value = fastDecode(value) || value;
|
||
|
}
|
||
|
}
|
||
|
const currentValue = result[key];
|
||
|
|
||
|
if (currentValue === undefined) {
|
||
|
result[key] = value;
|
||
|
} else {
|
||
|
// Optimization: value.pop is faster than Array.isArray(value)
|
||
|
if (currentValue.pop) {
|
||
|
currentValue.push(value);
|
||
|
} else {
|
||
|
result[key] = [currentValue, value];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Reset reading key value pairs
|
||
|
value = "";
|
||
|
startingIndex = i;
|
||
|
equalityIndex = i;
|
||
|
shouldDecodeKey = false;
|
||
|
shouldDecodeValue = false;
|
||
|
keyHasPlus = false;
|
||
|
valueHasPlus = false;
|
||
|
}
|
||
|
// Check '='
|
||
|
else if (c === 61) {
|
||
|
if (equalityIndex <= startingIndex) {
|
||
|
equalityIndex = i;
|
||
|
}
|
||
|
// If '=' character occurs again, we should decode the input.
|
||
|
else {
|
||
|
shouldDecodeValue = true;
|
||
|
}
|
||
|
}
|
||
|
// Check '+', and remember to replace it with empty space.
|
||
|
else if (c === 43) {
|
||
|
if (equalityIndex > startingIndex) {
|
||
|
valueHasPlus = true;
|
||
|
} else {
|
||
|
keyHasPlus = true;
|
||
|
}
|
||
|
}
|
||
|
// Check '%' character for encoding
|
||
|
else if (c === 37) {
|
||
|
if (equalityIndex > startingIndex) {
|
||
|
shouldDecodeValue = true;
|
||
|
} else {
|
||
|
shouldDecodeKey = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
module.exports = parse;
|