Browse Source

A replacement for decodeURIComponent that doesn't throw.

And add a few more tests.
v0.7.4-release
isaacs 15 years ago
committed by Ryan Dahl
parent
commit
4ce100fa62
  1. 34
      lib/querystring.js
  2. 81
      src/node_http_parser.cc
  3. 4
      test/simple/test-querystring.js

34
lib/querystring.js

@ -1,10 +1,10 @@
// Query String Utilities
var QueryString = exports;
var urlDecode = process.binding('http_parser').urlDecode;
QueryString.unescape = function (str, decodeSpaces) {
return decodeURIComponent(decodeSpaces ? str.replace(/\+/g, " ") : str);
};
// a safe fast alternative to decodeURIComponent
QueryString.unescape = urlDecode;
QueryString.escape = function (str) {
return encodeURIComponent(str);
@ -25,17 +25,17 @@ var stack = [];
* @param name {String} (optional) Name of the current key, for handling children recursively.
* @static
*/
QueryString.stringify = function (obj, sep, eq, munge, name) {
QueryString.stringify = QueryString.encode = function (obj, sep, eq, munge, name) {
munge = typeof(munge) == "undefined" ? true : munge;
sep = sep || "&";
eq = eq || "=";
if (isA(obj, null) || isA(obj, undefined) || typeof(obj) === 'function') {
return name ? encodeURIComponent(name) + eq : '';
return name ? QueryString.escape(name) + eq : '';
}
if (isBool(obj)) obj = +obj;
if (isNumber(obj) || isString(obj)) {
return encodeURIComponent(name) + eq + encodeURIComponent(obj);
return QueryString.escape(name) + eq + QueryString.escape(obj);
}
if (isA(obj, [])) {
var s = [];
@ -71,11 +71,11 @@ QueryString.stringify = function (obj, sep, eq, munge, name) {
return s;
};
QueryString.parseQuery = QueryString.parse = function (qs, sep, eq) {
QueryString.parse = QueryString.decode = function (qs, sep, eq) {
return (qs || '')
.split(sep||"&")
.map(pieceParser(eq||"="))
.reduce(mergeParams);
.reduce(mergeParams)
};
// Parse a key=val string.
@ -87,26 +87,14 @@ QueryString.parseQuery = QueryString.parse = function (qs, sep, eq) {
// return parse(foo[bar], [{bla:"baz"}])
// return parse(foo, {bar:[{bla:"baz"}]})
// return {foo:{bar:[{bla:"baz"}]}}
var trimmerPattern = /^\s+|\s+$/g,
slicerPattern = /(.*)\[([^\]]*)\]$/;
var slicerPattern = /(.*)\[([^\]]*)\]$/;
var pieceParser = function (eq) {
return function parsePiece (key, val) {
if (arguments.length !== 2) {
// key=val, called from the map/reduce
key = key.split(eq);
return parsePiece(
QueryString.unescape(key.shift(), true),
QueryString.unescape(key.join(eq), true)
);
}
key = key.replace(trimmerPattern, '');
if (isString(val)) {
val = val.replace(trimmerPattern, '');
// convert numerals to numbers
if (!isNaN(val)) {
var numVal = +val;
if (val === numVal.toString(10)) val = numVal;
}
return parsePiece(QueryString.unescape(key.shift(), true),
QueryString.unescape(key.join(eq), true));
}
var sliced = slicerPattern.exec(key);
if (!sliced) {

81
src/node_http_parser.cc

@ -315,6 +315,86 @@ class Parser : public ObjectWrap {
};
static Handle<Value> UrlDecode (const Arguments& args) {
HandleScope scope;
if (!args[0]->IsString()) {
return ThrowException(Exception::TypeError(
String::New("First arg must be a string")));
}
bool decode_spaces = args[1]->IsTrue();
String::Utf8Value in_v(args[0]->ToString());
size_t l = in_v.length();
char* out = strdup(*in_v);
enum { CHAR, HEX0, HEX1 } state = CHAR;
int n, m, hexchar;
size_t in_index = 0, out_index = 0;
char c;
for (; in_index <= l; in_index++) {
c = out[in_index];
switch (state) {
case CHAR:
switch (c) {
case '%':
n = 0;
m = 0;
state = HEX0;
break;
case '+':
if (decode_spaces) c = ' ';
// pass thru
default:
out[out_index++] = c;
break;
}
break;
case HEX0:
state = HEX1;
hexchar = c;
if ('0' <= c && c <= '9') {
n = c - '0';
} else if ('a' <= c && c <= 'f') {
n = c - 'a' + 10;
} else if ('A' <= c && c <= 'F') {
n = c - 'A' + 10;
} else {
out[out_index++] = '%';
out[out_index++] = c;
state = CHAR;
break;
}
break;
case HEX1:
state = CHAR;
if ('0' <= c && c <= '9') {
m = c - '0';
} else if ('a' <= c && c <= 'f') {
m = c - 'a' + 10;
} else if ('A' <= c && c <= 'F') {
m = c - 'A' + 10;
} else {
out[out_index++] = '%';
out[out_index++] = hexchar;
out[out_index++] = c;
break;
}
out[out_index++] = 16*n + m;
break;
}
}
Local<String> out_v = String::New(out, out_index-1);
free(out);
return scope.Close(out_v);
}
void InitHttpParser(Handle<Object> target) {
HandleScope scope;
@ -327,6 +407,7 @@ void InitHttpParser(Handle<Object> target) {
NODE_SET_PROTOTYPE_METHOD(t, "reinitialize", Parser::Reinitialize);
target->Set(String::NewSymbol("HTTPParser"), t->GetFunction());
NODE_SET_METHOD(target, "urlDecode", UrlDecode);
on_message_begin_sym = NODE_PSYMBOL("onMessageBegin");
on_path_sym = NODE_PSYMBOL("onPath");

4
test/simple/test-querystring.js

@ -29,7 +29,9 @@ var qsTestCases = [
["foo[bar][bla]=baz&foo[bar][bla]=blo", "foo%5Bbar%5D%5Bbla%5D%5B%5D=baz&foo%5Bbar%5D%5Bbla%5D%5B%5D=blo", {"foo":{"bar":{"bla":["baz","blo"]}}}],
["foo[bar][][bla]=baz&foo[bar][][bla]=blo", "foo%5Bbar%5D%5B%5D%5Bbla%5D=baz&foo%5Bbar%5D%5B%5D%5Bbla%5D=blo", {"foo":{"bar":[{"bla":"baz"},{"bla":"blo"}]}}],
["foo[bar][bla][]=baz&foo[bar][bla][]=blo", "foo%5Bbar%5D%5Bbla%5D%5B%5D=baz&foo%5Bbar%5D%5Bbla%5D%5B%5D=blo", {"foo":{"bar":{"bla":["baz","blo"]}}}],
[" foo = bar ", "foo=bar", {"foo":"bar"}]
[" foo = bar ", "%20foo%20=%20bar%20", {" foo ":" bar "}],
["foo=%zx", "foo=%25zx", {"foo":"%zx"}],
["foo=%EF%BF%BD", "foo=%EF%BF%BD", {"foo" : "\ufffd" }]
];
// [ wonkyQS, canonicalQS, obj ]

Loading…
Cancel
Save