node/lib/querystring.js
Myles Borins 878bcd43f8 querystring: check that maxKeys is finite
There was a very subtle change in behavior introduced with 27def4f

In the past if querystring.parse was given Infinity for maxKeys,
everything worked as expected.

Check to see is maxKeys is Infinity before forwarding the value to
String.prototype.split which causes this regression

PR-URL: https://github.com/nodejs/node/pull/5066
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
2016-02-05 13:58:50 -08:00

291 lines
7.1 KiB
JavaScript

// Query String Utilities
'use strict';
const QueryString = exports;
const Buffer = require('buffer').Buffer;
function charCode(c) {
return c.charCodeAt(0);
}
// a safe fast alternative to decodeURIComponent
QueryString.unescapeBuffer = function(s, decodeSpaces) {
var out = new Buffer(s.length);
var state = 'CHAR'; // states: CHAR, HEX0, HEX1
var n, m, hexchar;
for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) {
var c = s.charCodeAt(inIndex);
switch (state) {
case 'CHAR':
switch (c) {
case charCode('%'):
n = 0;
m = 0;
state = 'HEX0';
break;
case charCode('+'):
if (decodeSpaces) c = charCode(' ');
// falls through
default:
out[outIndex++] = c;
break;
}
break;
case 'HEX0':
state = 'HEX1';
hexchar = c;
if (charCode('0') <= c && c <= charCode('9')) {
n = c - charCode('0');
} else if (charCode('a') <= c && c <= charCode('f')) {
n = c - charCode('a') + 10;
} else if (charCode('A') <= c && c <= charCode('F')) {
n = c - charCode('A') + 10;
} else {
out[outIndex++] = charCode('%');
out[outIndex++] = c;
state = 'CHAR';
break;
}
break;
case 'HEX1':
state = 'CHAR';
if (charCode('0') <= c && c <= charCode('9')) {
m = c - charCode('0');
} else if (charCode('a') <= c && c <= charCode('f')) {
m = c - charCode('a') + 10;
} else if (charCode('A') <= c && c <= charCode('F')) {
m = c - charCode('A') + 10;
} else {
out[outIndex++] = charCode('%');
out[outIndex++] = hexchar;
out[outIndex++] = c;
break;
}
out[outIndex++] = 16 * n + m;
break;
}
}
// TODO support returning arbitrary buffers.
return out.slice(0, outIndex - 1);
};
QueryString.unescape = function(s, decodeSpaces) {
try {
return decodeURIComponent(s);
} catch (e) {
return QueryString.unescapeBuffer(s, decodeSpaces).toString();
}
};
var hexTable = new Array(256);
for (var i = 0; i < 256; ++i)
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
QueryString.escape = function(str) {
// replaces encodeURIComponent
// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
str = '' + str;
var len = str.length;
var out = '';
var i, c;
if (len === 0)
return str;
for (i = 0; i < len; ++i) {
c = str.charCodeAt(i);
// These characters do not need escaping (in order):
// ! - . _ ~
// ' ( ) *
// digits
// alpha (uppercase)
// alpha (lowercase)
if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E ||
(c >= 0x27 && c <= 0x2A) ||
(c >= 0x30 && c <= 0x39) ||
(c >= 0x41 && c <= 0x5A) ||
(c >= 0x61 && c <= 0x7A)) {
out += str[i];
continue;
}
// Other ASCII characters
if (c < 0x80) {
out += hexTable[c];
continue;
}
// Multi-byte characters ...
if (c < 0x800) {
out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)];
continue;
}
if (c < 0xD800 || c >= 0xE000) {
out += hexTable[0xE0 | (c >> 12)] +
hexTable[0x80 | ((c >> 6) & 0x3F)] +
hexTable[0x80 | (c & 0x3F)];
continue;
}
// Surrogate pair
++i;
c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(i) & 0x3FF));
out += hexTable[0xF0 | (c >> 18)] +
hexTable[0x80 | ((c >> 12) & 0x3F)] +
hexTable[0x80 | ((c >> 6) & 0x3F)] +
hexTable[0x80 | (c & 0x3F)];
}
return out;
};
var stringifyPrimitive = function(v) {
if (typeof v === 'string')
return v;
if (typeof v === 'number' && isFinite(v))
return '' + v;
if (typeof v === 'boolean')
return v ? 'true' : 'false';
return '';
};
QueryString.stringify = QueryString.encode = function(obj, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
var encode = QueryString.escape;
if (options && typeof options.encodeURIComponent === 'function') {
encode = options.encodeURIComponent;
}
if (obj !== null && typeof obj === 'object') {
var keys = Object.keys(obj);
var len = keys.length;
var flast = len - 1;
var fields = '';
for (var i = 0; i < len; ++i) {
var k = keys[i];
var v = obj[k];
var ks = encode(stringifyPrimitive(k)) + eq;
if (Array.isArray(v)) {
var vlen = v.length;
var vlast = vlen - 1;
for (var j = 0; j < vlen; ++j) {
fields += ks + encode(stringifyPrimitive(v[j]));
if (j < vlast)
fields += sep;
}
if (vlen && i < flast)
fields += sep;
} else {
fields += ks + encode(stringifyPrimitive(v));
if (i < flast)
fields += sep;
}
}
return fields;
}
return '';
};
// Parse a key=val string.
QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
const eqLen = eq.length;
var obj = {};
if (typeof qs !== 'string' || qs.length === 0) {
return obj;
}
var maxKeys = 1000;
if (options && typeof options.maxKeys === 'number') {
maxKeys = options.maxKeys;
}
// maxKeys <= 0 means that we should not limit keys count
if (maxKeys > 0 && isFinite(maxKeys)) {
qs = qs.split(sep, maxKeys);
} else {
qs = qs.split(sep);
}
var len = qs.length;
var decode = QueryString.unescape;
if (options && typeof options.decodeURIComponent === 'function') {
decode = options.decodeURIComponent;
}
var keys = [];
for (var i = 0; i < len; ++i) {
// replacePlus() is used instead of a regexp because it is ~15-30% faster
// with v8 4.7
const x = replacePlus(qs[i]);
const idx = x.indexOf(eq);
var k, v;
if (idx >= 0) {
k = decodeStr(x.substring(0, idx), decode);
v = decodeStr(x.substring(idx + eqLen), decode);
} else {
k = decodeStr(x, decode);
v = '';
}
// Use a key array lookup instead of using hasOwnProperty(), which is slower
if (keys.indexOf(k) === -1) {
obj[k] = v;
keys.push(k);
} else if (obj[k] instanceof Array) {
// `instanceof Array` is used instead of Array.isArray() because it is
// ~15-20% faster with v8 4.7 and is safe to use because we are using it
// with values being created within this function
obj[k].push(v);
} else {
obj[k] = [obj[k], v];
}
}
return obj;
};
function replacePlus(str) {
var ret = '';
var start = 0;
var i = -1;
while ((i = str.indexOf('+', i + 1)) !== -1) {
ret += str.slice(start, i);
ret += '%20';
start = i + 1;
}
if (start === 0)
return str;
if (start < str.length)
ret += str.slice(start);
return ret;
}
// v8 does not optimize functions with try-catch blocks, so we isolate them here
// to minimize the damage
function decodeStr(s, decoder) {
try {
return decoder(s);
} catch (e) {
return QueryString.unescape(s, true);
}
}