opusdev/vector-similarity-api
1
1'use strict';2 3var utils = require('./utils');4 5var has = Object.prototype.hasOwnProperty;6var isArray = Array.isArray;7 8var defaults = {9 allowDots: false,10 allowEmptyArrays: false,11 allowPrototypes: false,12 allowSparse: false,13 arrayLimit: 20,14 charset: 'utf-8',15 charsetSentinel: false,16 comma: false,17 decodeDotInKeys: false,18 decoder: utils.decode,19 delimiter: '&',20 depth: 5,21 duplicates: 'combine',22 ignoreQueryPrefix: false,23 interpretNumericEntities: false,24 parameterLimit: 1000,25 parseArrays: true,26 plainObjects: false,27 strictDepth: false,28 strictNullHandling: false,29 throwOnLimitExceeded: false30};31 32var interpretNumericEntities = function (str) {33 return str.replace(/&#(\d+);/g, function ($0, numberStr) {34 return String.fromCharCode(parseInt(numberStr, 10));35 });36};37 38var parseArrayValue = function (val, options, currentArrayLength) {39 if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {40 return val.split(',');41 }42 43 if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {44 throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');45 }46 47 return val;48};49 50// This is what browsers will submit when the ✓ character occurs in an51// application/x-www-form-urlencoded body and the encoding of the page containing52// the form is iso-8859-1, or when the submitted form has an accept-charset53// attribute of iso-8859-1. Presumably also with other charsets that do not contain54// the ✓ character, such as us-ascii.55var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓')56 57// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded.58var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')59 60var parseValues = function parseQueryStringValues(str, options) {61 var obj = { __proto__: null };62 63 var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;64 cleanStr = cleanStr.replace(/%5B/gi, '[').replace(/%5D/gi, ']');65 66 var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;67 var parts = cleanStr.split(68 options.delimiter,69 options.throwOnLimitExceeded ? limit + 1 : limit70 );71 72 if (options.throwOnLimitExceeded && parts.length > limit) {73 throw new RangeError('Parameter limit exceeded. Only ' + limit + ' parameter' + (limit === 1 ? '' : 's') + ' allowed.');74 }75 76 var skipIndex = -1; // Keep track of where the utf8 sentinel was found77 var i;78 79 var charset = options.charset;80 if (options.charsetSentinel) {81 for (i = 0; i < parts.length; ++i) {82 if (parts[i].indexOf('utf8=') === 0) {83 if (parts[i] === charsetSentinel) {84 charset = 'utf-8';85 } else if (parts[i] === isoSentinel) {86 charset = 'iso-8859-1';87 }88 skipIndex = i;89 i = parts.length; // The eslint settings do not allow break;90 }91 }92 }93 94 for (i = 0; i < parts.length; ++i) {95 if (i === skipIndex) {96 continue;97 }98 var part = parts[i];99 100 var bracketEqualsPos = part.indexOf(']=');101 var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;102 103 var key;104 var val;105 if (pos === -1) {106 key = options.decoder(part, defaults.decoder, charset, 'key');107 val = options.strictNullHandling ? null : '';108 } else {109 key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');110 111 if (key !== null) {112 val = utils.maybeMap(113 parseArrayValue(114 part.slice(pos + 1),115 options,116 isArray(obj[key]) ? obj[key].length : 0117 ),118 function (encodedVal) {119 return options.decoder(encodedVal, defaults.decoder, charset, 'value');120 }121 );122 }123 }124 125 if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {126 val = interpretNumericEntities(String(val));127 }128 129 if (part.indexOf('[]=') > -1) {130 val = isArray(val) ? [val] : val;131 }132 133 if (key !== null) {134 var existing = has.call(obj, key);135 if (existing && options.duplicates === 'combine') {136 obj[key] = utils.combine(137 obj[key],138 val,139 options.arrayLimit,140 options.plainObjects141 );142 } else if (!existing || options.duplicates === 'last') {143 obj[key] = val;144 }145 }146 }147 148 return obj;149};150 151var parseObject = function (chain, val, options, valuesParsed) {152 var currentArrayLength = 0;153 if (chain.length > 0 && chain[chain.length - 1] === '[]') {154 var parentKey = chain.slice(0, -1).join('');155 currentArrayLength = Array.isArray(val) && val[parentKey] ? val[parentKey].length : 0;156 }157 158 var leaf = valuesParsed ? val : parseArrayValue(val, options, currentArrayLength);159 160 for (var i = chain.length - 1; i >= 0; --i) {161 var obj;162 var root = chain[i];163 164 if (root === '[]' && options.parseArrays) {165 if (utils.isOverflow(leaf)) {166 // leaf is already an overflow object, preserve it167 obj = leaf;168 } else {169 obj = options.allowEmptyArrays && (leaf === '' || (options.strictNullHandling && leaf === null))170 ? []171 : utils.combine(172 [],173 leaf,174 options.arrayLimit,175 options.plainObjects176 );177 }178 } else {179 obj = options.plainObjects ? { __proto__: null } : {};180 var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;181 var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/g, '.') : cleanRoot;182 var index = parseInt(decodedRoot, 10);183 if (!options.parseArrays && decodedRoot === '') {184 obj = { 0: leaf };185 } else if (186 !isNaN(index)187 && root !== decodedRoot188 && String(index) === decodedRoot189 && index >= 0190 && (options.parseArrays && index <= options.arrayLimit)191 ) {192 obj = [];193 obj[index] = leaf;194 } else if (decodedRoot !== '__proto__') {195 obj[decodedRoot] = leaf;196 }197 }198 199 leaf = obj;200 }201 202 return leaf;203};204 205var splitKeyIntoSegments = function splitKeyIntoSegments(givenKey, options) {206 var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey;207 208 if (options.depth <= 0) {209 if (!options.plainObjects && has.call(Object.prototype, key)) {210 if (!options.allowPrototypes) {211 return;212 }213 }214 215 return [key];216 }217 218 var brackets = /(\[[^[\]]*])/;219 var child = /(\[[^[\]]*])/g;220 221 var segment = brackets.exec(key);222 var parent = segment ? key.slice(0, segment.index) : key;223 224 var keys = [];225 226 if (parent) {227 if (!options.plainObjects && has.call(Object.prototype, parent)) {228 if (!options.allowPrototypes) {229 return;230 }231 }232 233 keys.push(parent);234 }235 236 var i = 0;237 while ((segment = child.exec(key)) !== null && i < options.depth) {238 i += 1;239 240 var segmentContent = segment[1].slice(1, -1);241 if (!options.plainObjects && has.call(Object.prototype, segmentContent)) {242 if (!options.allowPrototypes) {243 return;244 }245 }246 247 keys.push(segment[1]);248 }249 250 if (segment) {251 if (options.strictDepth === true) {252 throw new RangeError('Input depth exceeded depth option of ' + options.depth + ' and strictDepth is true');253 }254 255 keys.push('[' + key.slice(segment.index) + ']');256 }257 258 return keys;259};260 261var parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {262 if (!givenKey) {263 return;264 }265 266 var keys = splitKeyIntoSegments(givenKey, options);267 268 if (!keys) {269 return;270 }271 272 return parseObject(keys, val, options, valuesParsed);273};274 275var normalizeParseOptions = function normalizeParseOptions(opts) {276 if (!opts) {277 return defaults;278 }279 280 if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') {281 throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided');282 }283 284 if (typeof opts.decodeDotInKeys !== 'undefined' && typeof opts.decodeDotInKeys !== 'boolean') {285 throw new TypeError('`decodeDotInKeys` option can only be `true` or `false`, when provided');286 }287 288 if (opts.decoder !== null && typeof opts.decoder !== 'undefined' && typeof opts.decoder !== 'function') {289 throw new TypeError('Decoder has to be a function.');290 }291 292 if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {293 throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');294 }295 296 if (typeof opts.throwOnLimitExceeded !== 'undefined' && typeof opts.throwOnLimitExceeded !== 'boolean') {297 throw new TypeError('`throwOnLimitExceeded` option must be a boolean');298 }299 300 var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset;301 302 var duplicates = typeof opts.duplicates === 'undefined' ? defaults.duplicates : opts.duplicates;303 304 if (duplicates !== 'combine' && duplicates !== 'first' && duplicates !== 'last') {305 throw new TypeError('The duplicates option must be either combine, first, or last');306 }307 308 var allowDots = typeof opts.allowDots === 'undefined' ? opts.decodeDotInKeys === true ? true : defaults.allowDots : !!opts.allowDots;309 310 return {311 allowDots: allowDots,312 allowEmptyArrays: typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays,313 allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes,314 allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse,315 arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit,316 charset: charset,317 charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,318 comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma,319 decodeDotInKeys: typeof opts.decodeDotInKeys === 'boolean' ? opts.decodeDotInKeys : defaults.decodeDotInKeys,320 decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder,321 delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter,322 // eslint-disable-next-line no-implicit-coercion, no-extra-parens323 depth: (typeof opts.depth === 'number' || opts.depth === false) ? +opts.depth : defaults.depth,324 duplicates: duplicates,325 ignoreQueryPrefix: opts.ignoreQueryPrefix === true,326 interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities,327 parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,328 parseArrays: opts.parseArrays !== false,329 plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,330 strictDepth: typeof opts.strictDepth === 'boolean' ? !!opts.strictDepth : defaults.strictDepth,331 strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling,332 throwOnLimitExceeded: typeof opts.throwOnLimitExceeded === 'boolean' ? opts.throwOnLimitExceeded : false333 };334};335 336module.exports = function (str, opts) {337 var options = normalizeParseOptions(opts);338 339 if (str === '' || str === null || typeof str === 'undefined') {340 return options.plainObjects ? { __proto__: null } : {};341 }342 343 var tempObj = typeof str === 'string' ? parseValues(str, options) : str;344 var obj = options.plainObjects ? { __proto__: null } : {};345 346 // Iterate over the keys and setup the new object347 348 var keys = Object.keys(tempObj);349 for (var i = 0; i < keys.length; ++i) {350 var key = keys[i];351 var newObj = parseKeys(key, tempObj[key], options, typeof str === 'string');352 obj = utils.merge(obj, newObj, options);353 }354 355 if (options.allowSparse === true) {356 return obj;357 }358 359 return utils.compact(obj);360};361 