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

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;