urlencode.js 4.84 KB
/**!
 * urlencode - lib/urlencode.js
 *
 * MIT Licensed
 *
 * Authors:
 *   fengmk2 <fengmk2@gmail.com> (http://fengmk2.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 = '';
  var ch = '';
  for (var i = 0; i < buf.length; i++) {
    ch = buf[i].toString('16');
    if (ch.length === 1) {
      ch = '0' + ch;
    }
    encodeStr += '%' + ch;
  }
  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);
}

function parse(qs, sep, eq, options) {
  if (typeof sep === 'object') {
    // parse(qs, options)
    options = sep;
    sep = null;
  }

  sep = sep || '&';
  eq = eq || '=';
  var obj = {};

  if (typeof qs !== 'string' || qs.length === 0) {
    return obj;
  }

  var regexp = /\+/g;
  qs = qs.split(sep);

  var maxKeys = 1000;
  var charset = null;
  if (options) {
    if (typeof options.maxKeys === 'number') {
      maxKeys = options.maxKeys;
    }
    if (typeof options.charset === 'string') {
      charset = options.charset;
    }
  }

  var len = qs.length;
  // maxKeys <= 0 means that we should not limit keys count
  if (maxKeys > 0 && len > maxKeys) {
    len = maxKeys;
  }

  for (var i = 0; i < len; ++i) {
    var x = qs[i].replace(regexp, '%20');
    var idx = x.indexOf(eq);
    var kstr, vstr, k, v;

    if (idx >= 0) {
      kstr = x.substr(0, idx);
      vstr = x.substr(idx + 1);
    } else {
      kstr = x;
      vstr = '';
    }

    if (kstr && kstr.indexOf('%') >= 0) {
      try {
        k = decode(kstr, charset);
      } catch (e) {
        k = kstr;
      }
    } else {
      k = kstr;
    }

    if (vstr && vstr.indexOf('%') >= 0) {
      try {
        v = decode(vstr, charset);
      } catch (e) {
        v = vstr;
      }
    } else {
      v = vstr;
    }

    if (!has(obj, k)) {
      obj[k] = v;
    } else if (Array.isArray(obj[k])) {
      obj[k].push(v);
    } else {
      obj[k] = [obj[k], v];
    }
  }

  return obj;
}

function has(obj, prop) {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}

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 = encode;
module.exports.encode = encode;
module.exports.decode = decode;
module.exports.parse = parse;
module.exports.stringify = stringify;