UNPKG

querystringparser

Version:
407 lines (367 loc) 11.3 kB
/** * Copyright (c) 2013 Petka Antonov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions:</p> * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ "use strict"; var QueryStringSerializer = require("./querystringserializer.js"); module.exports = QueryStringParser; var rplus = /\+/g; var rint = /^[0-9]+$/; var isArray = Array.isArray; var haveProp = {}.hasOwnProperty; function QueryStringParser() { this.containsSparse = false; this.cacheKey = ""; this.cacheVal = null; } QueryStringParser.maxLength = 32768; QueryStringParser.maxDepth = 4; QueryStringParser.maxKeys = 256; QueryStringParser.parse = function QueryStringParser$Parse(str) { if (typeof str === "string") { var maxLength = QueryStringParser.maxLength; if (str.length > maxLength) { throw new RangeError( "str is too large (" + "QueryStringParser.maxLength=" + maxLength + ")" ); } var parser = new QueryStringParser(); return parser.parseString(str, false); } else if (str !== null && typeof str === "object") { var parser = new QueryStringParser(); return parser.parseObject(str); } return {}; }; QueryStringParser.stringify = function QueryStringParser$Stringify(value) { var serializer = new QueryStringSerializer(); return serializer.serialize(value); }; QueryStringParser.prototype.decode = function QueryStringParser$decode(str, shouldDecode, containsPlus) { if (shouldDecode === false) return str; if (containsPlus === true) str = str.replace(rplus, " "); try { return decodeURIComponent(str); } catch (e) { return str; } }; QueryStringParser.prototype.maybeArrayIndex = function QueryStringParser$maybeArrayIndex(str, arrayLength) { var len = str.length; if (len === 0) { return arrayLength; } var ch = str.charCodeAt(0); if (ch === 48) { return len > 1 ? -1 : 0; } else if (48 <= ch && ch <= 57) { if (len === 1) { return ch - 48; } else if (rint.test(str)) { var v = parseInt(str, 10); if (0 < v && v <= 1073741822) { return v; } } } return -1; }; QueryStringParser.prototype.getSlot = function QueryStringParser$getSlot(dictionary, prevKey, curKey) { var slot; if (!(haveProp.call(dictionary, prevKey))) { var index = this.maybeArrayIndex(curKey, 0); if (index > -1) { slot = []; } else { slot = {}; } dictionary[prevKey] = slot; } else { slot = dictionary[prevKey]; } return slot; }; QueryStringParser.prototype.placeNestedValue = function QueryStringParser$placeNestedValue (dictionary, key, value, i, prevKey, curKey) { var slot = this.getSlot(dictionary, prevKey, curKey); var index = -1; if (isArray(slot)) { index = this.maybeArrayIndex(curKey, slot.length); } var len = key.length; var depth = 2; var maxDepth = QueryStringParser.maxDepth; var start = -1; for (; i < len; ++i) { var ch = key.charCodeAt(i); if (ch === 91) { start = i + 1; } else if (ch === 93 && start > -1) { prevKey = curKey; curKey = start === i ? "" : key.substring(start, i); start = -1; depth++; if (depth > maxDepth) { throw new RangeError("Nesting depth of keys is too large " + "(QueryStringParser.maxDepth="+maxDepth+")" ); } slot = this.getSlot(slot, prevKey, curKey); index = isArray(slot) ? this.maybeArrayIndex(curKey, slot.length) : -1; } } if(index > -1) { if (value !== "") { if (index === slot.length) { slot.push(value); } else { this.containsSparse = true; slot[index] = value; } } } else { this.insert(slot, curKey, value); } }; QueryStringParser.prototype.insert = function QueryStringParser$insert(dictionary, key, value) { var ret = null; if (haveProp.call(dictionary, key)) { var prev = dictionary[key]; if( isArray(prev) ) { prev.push(value); ret = prev; } else { ret = [prev, value]; dictionary[key] = ret; } } else { dictionary[key] = value; } return ret; }; QueryStringParser.prototype.push = function QueryStringParser$push(dictionary, key, value) { var ret = null; if (haveProp.call(dictionary, key)) { var prev = dictionary[key]; prev.push(value); ret = prev; } else { ret = [value]; dictionary[key] = ret; } return ret; }; QueryStringParser.prototype.maybePlaceNestedValue = function QueryStringParser$maybePlaceNestedValue(dictionary, key, value) { var len = key.length; if (key.charCodeAt(len - 1) !== 93) { this.placeValue(dictionary, key, value, false); return; } var start = -1; var i = 0; var curKey; var prevKey; for (; i < len; ++i) { var ch = key.charCodeAt(i); if (ch === 91) { start = i + 1; prevKey = key.slice(0, i); } else if (ch === 93) { if (start < 0) { this.placeValue(dictionary, key, value, false); return; } curKey = start === i ? "" : key.slice(start, i); i++; break; } } if (curKey === void 0) { this.placeValue(dictionary, key, value, false); return; } if (curKey === "" && value !== "" && i === len) { if (key === this.cacheKey) { this.cacheVal.push(value); } else { this.cacheKey = key; this.cacheVal = this.push(dictionary, prevKey, value); } } else { this.placeNestedValue(dictionary, key, value, i, prevKey, curKey); } }; QueryStringParser.prototype.placeValue = function QueryStringParser$placeValue(dictionary, key, value, possiblyNested) { if (possiblyNested === true) { this.maybePlaceNestedValue(dictionary, key, value); return; } if (key === this.cacheKey) { this.cacheVal.push(value); return; } var cache = this.insert(dictionary, key, value); if (cache !== null) { this.cacheKey = key; this.cacheVal = cache; } }; QueryStringParser.prototype.compact = function QueryStringParser$compact(obj) { if (isArray(obj)) { var ret = []; var keys = Object.keys(obj); for( var i = 0, len = keys.length; i < len; ++i ) { ret.push(obj[keys[i]]); } return ret; } else if (typeof obj === "object") { var keys = Object.keys(obj); for( var i = 0, len = keys.length; i < len; ++i ) { var key = keys[i]; obj[key] = this.compact(obj[key]); } } else { return obj; } }; QueryStringParser.prototype.parseObject = function QueryStringParser$parseObject(obj) { var keys = Object.keys(obj); var len = keys.length; if (len === 0) { return {}; } len--; var ret = ""; var key; for( var i = 0; i < len; ++i ) { key = keys[i]; ret += key + "=" + obj[key] + "&"; } key = keys[i]; ret += key + "=" + obj[key]; return this.parseString(ret, true); }; QueryStringParser.prototype.parseString = function QueryStringParser$parseString(str, noDecode) { var maxKeys = QueryStringParser.maxKeys; var keys = 0; var decodeKey = false; var decodeValue = false; var possiblyNested = false; var len = str.length; var i = 0; var dictionary = {}; var keyStart = 0; var keyEnd = 0; var valueStart = 0; var valueEnd = 0; var left = 0; var lastIndex = len - 1; var containsPlus = false; for (; i < len; ++i) { var ch = str.charCodeAt(i); if (ch === 91) { left++; } else if (left > 0 && ch === 93) { possiblyNested = true; left--; } else if (left === 0 && ch === 61) { var j = i + 1; keyEnd = i - 1; valueEnd = valueStart = j; var key = str.slice(keyStart, keyEnd + 1); key = this.decode(key, decodeKey, containsPlus); decodeKey = false; for (; j < len; ++j) { ch = str.charCodeAt(j); if ((ch === 43 || ch === 37) && !noDecode) { if (ch === 43) containsPlus = true; decodeValue = true; } if (ch === 38 || j === lastIndex) { valueEnd = j; i = j; if (ch === 38) { valueEnd--; } var value = str.slice(valueStart, valueEnd + 1); value = this.decode(value, decodeValue, containsPlus); this.placeValue(dictionary, key, value, possiblyNested); containsPlus = decodeValue = false; possiblyNested = false; keyStart = j + 1; keys++; if (keys > maxKeys) { throw new RangeError("Amount of keys is too large " + "(QueryStringParser.maxKeys=" + maxKeys + ")"); } break; } } } else if ((ch === 43 || ch === 37) && !noDecode) { if (ch === 43) containsPlus = true; decodeKey = true; } } if (keyStart !== len) { var value = ""; var key = str.slice(keyStart, len); key = this.decode(key, decodeKey, containsPlus); this.placeValue(dictionary, key, value, possiblyNested); } if (this.containsSparse) { this.compact(dictionary); } return dictionary; };