Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require('./lib/urlencode');
module.exports = require('./lib/index');
66 changes: 66 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**!
* urlencode - lib/urlencode.js
*
* Copyright(c) 2012 - 2014
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/

"use strict";

/**
* Module dependencies.
*/

var iconv = require('iconv-lite');

function isUTF8(charset) {
if (!charset) {
return true;
}
charset = charset.toLowerCase();
return charset === 'utf8' || charset === 'utf-8';
}

function encode(str, charset) {
if (isUTF8(charset)) {
return encodeURIComponent(str);
}

var buf = iconv.encode(str, charset);
var encodeStr = '';
for (var i = 0; i < buf.length; i++) {
encodeStr += '%' + buf[i].toString('16');
}
encodeStr = encodeStr.toUpperCase();
return encodeStr;
}

function decode(str, charset) {
if (isUTF8(charset)) {
return decodeURIComponent(str);
}

var bytes = [];
for (var i = 0; i < str.length; ) {
if (str[i] === '%') {
i++;
bytes.push(parseInt(str.substring(i, i + 2), 16));
i += 2;
} else {
bytes.push(str.charCodeAt(i));
i++;
}
}
var buf = new Buffer(bytes);
return iconv.decode(buf, charset);
}


module.exports = encode;
module.exports.encode = encode;
module.exports.decode = decode;
module.exports.parse = require('./parse');
module.exports.stringify = require('./stringify');
219 changes: 219 additions & 0 deletions lib/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
"use strict";

var decode = require('./index').decode;

/**
* Cache non-integer test regexp.
*/

var isint = /^[0-9]+$/;

function promote(parent, key) {
if (parent[key].length === 0) {
return parent[key] = {};
}
var t = {};
for (var i in parent[key]) {
if (parent[key].hasOwnProperty(i)) {
t[i] = parent[key][i];
}
}
parent[key] = t;
return t;
}

function parse(parts, parent, key, val) {
var part = parts.shift();

// illegal
if (Object.hasOwnProperty.call(Object.prototype, key)) {
return;
}

// end
if (!part) {
if (Array.isArray(parent[key])) {
parent[key].push(val);
} else if ('object' === typeof parent[key]) {
parent[key] = val;
} else if ('undefined' === typeof parent[key]) {
parent[key] = val;
} else {
parent[key] = [parent[key], val];
}
// array
} else {
var obj = parent[key] = parent[key] || [];
if (']' === part) {
if (Array.isArray(obj)) {
if ('' !== val) {
obj.push(val);
}
} else if ('object' === typeof obj) {
obj[Object.keys(obj).length] = val;
} else {
obj = parent[key] = [parent[key], val];
}
// prop
} else if (~part.indexOf(']')) {
part = part.substr(0, part.length - 1);
if (!isint.test(part) && Array.isArray(obj)) {
obj = promote(parent, key);
}
parse(parts, obj, part, val);
// key
} else {
if (!isint.test(part) && Array.isArray(obj)) {
obj = promote(parent, key);
}
parse(parts, obj, part, val);
}
}
}

/**
* Merge parent key/val pair.
*/

function merge(parent, key, val){
if (~key.indexOf(']')) {
var parts = key.split('[');
var len = parts.length;
var last = len - 1;
parse(parts, parent, 'base', val);
// optimize
} else {
if (!isint.test(key) && Array.isArray(parent.base)) {
var t = {};
for (var k in parent.base) {
t[k] = parent.base[k];
}
parent.base = t;
}
set(parent.base, key, val);
}

return parent;
}

/**
* Compact sparse arrays.
*/

function compact(obj) {
if ('object' !== typeof obj) {return obj;}

if (Array.isArray(obj)) {
var ret = [];

for (var i in obj) {
if (hasOwnProperty.call(obj, i)) {
ret.push(obj[i]);
}
}

return ret;
}

for (var key in obj) {
obj[key] = compact(obj[key]);
}

return obj;
}

/**
* Parse the given obj.
*/

function parseObject(obj){
var ret = { base: {} };

Object.keys(obj).forEach(function(name){
merge(ret, name, obj[name]);
});

return compact(ret.base);
}

/**
* Parse the given str.
*/

function parseString(str, options){
var ret = String(str).split('&').reduce(function(ret, pair){
var eql = pair.indexOf('=');
var brace = lastBraceInKey(pair);
var key = pair.substr(0, brace || eql);
var val = pair.substr(brace || eql, pair.length);
val = val.substr(val.indexOf('=') + 1, val.length);

// ?foo
if ('' === key) {key = pair, val = '';}
if ('' === key) {return ret;}

var charset = options.charset;
return merge(ret, decode(key), decode(val, charset));
}, { base: {} }).base;

return compact(ret);
}

/**
* Set `obj`'s `key` to `val` respecting
* the weird and wonderful syntax of a qs,
* where "foo=bar&foo=baz" becomes an array.
*
* @param {Object} obj
* @param {String} key
* @param {String} val
* @api private
*/

function set(obj, key, val) {
var v = obj[key];
if (Object.hasOwnProperty.call(Object.prototype, key)) {return;}
if (undefined === v) {
obj[key] = val;
} else if (Array.isArray(v)) {
v.push(val);
} else {
obj[key] = [v, val];
}
}

/**
* Locate last brace in `str` within the key.
*
* @param {String} str
* @return {Number}
* @api private
*/

function lastBraceInKey(str) {
var len = str.length;
var brace;
var c;
for (var i = 0; i < len; ++i) {
c = str[i];
if (']' === c) {brace = false;}
if ('[' === c) {brace = true;}
if ('=' === c && !brace) {return i;}
}
}

/**
* Parse the given query `str` or `obj`, returning an object.
*
* @param {String} str | {Object} obj
* @return {Object}
* @api public
*/

module.exports = function(str, options){
if (null === str || '' === str) {return {};}
if (!options) {options = {};}
return 'object' === typeof str
? parseObject(str)
: parseString(str, options);
};
79 changes: 79 additions & 0 deletions lib/stringify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use strict";

var encode = require('./index').encode;

function isASCII(str) {
return (/^[\x00-\x7F]*$/).test(str);
}

function encodeComponent(item, charset) {
item = String(item);
if (isASCII(item)) {
item = encodeURIComponent(item);
} else {
item = encode(item, charset);
}
return item;
}

var stringify = function(obj, prefix, options) {
if (typeof prefix !== 'string') {
options = prefix || {};
prefix = null;
}
var charset = options.charset || 'utf-8';
if (Array.isArray(obj)) {
return stringifyArray(obj, prefix, options);
} else if ('[object Object]' === {}.toString.call(obj)) {
return stringifyObject(obj, prefix, options);
} else if ('string' === typeof obj) {
return stringifyString(obj, prefix, options);
} else {
return prefix + '=' + encodeComponent(String(obj), charset);
}
};

function stringifyString(str, prefix, options) {
if (!prefix) {
throw new TypeError('stringify expects an object');
}
var charset = options.charset;
return prefix + '=' + encodeComponent(str, charset);
}

function stringifyArray(arr, prefix, options) {
var ret = [];
if (!prefix) {
throw new TypeError('stringify expects an object');
}
for (var i = 0; i < arr.length; i++) {
ret.push(stringify(arr[i], prefix + '[' + i + ']', options));
}
return ret.join('&');
}

function stringifyObject(obj, prefix, options) {
var ret = [];
var keys = Object.keys(obj);
var key;

var charset = options.charset;
for (var i = 0, len = keys.length; i < len; ++i) {
key = keys[i];
if ('' === key) {
continue;
}
if (null === obj[key]) {
ret.push(encode(key, charset) + '=');
} else {
ret.push(stringify(
obj[key],
prefix ? prefix + '[' + encodeComponent(key, charset) + ']': encodeComponent(key, charset),
options));
}
}

return ret.join('&');
}

module.exports = stringify;
Loading