index.js 4.22 KB
// muri

/**
 * MongoDB URI parser as described here:
 * http://www.mongodb.org/display/DOCS/Connections
 */

/**
 * Module dependencies
 */

var qs = require('querystring');

/**
 * Defaults
 */

var DEFAULT_PORT = 27017;
var DEFAULT_DB = 'test';
var ADMIN_DB = 'admin';

/**
 * Muri
 */

module.exports = exports = function muri (str) {
  if (!/^mongodb:\/\//.test(str)) {
    throw new Error('Invalid mongodb uri "' + str + '". Must begin with "mongodb://"');
  }

  var ret = {
      hosts: []
    , db: DEFAULT_DB
    , options: {}
  }

  var match = /^mongodb:\/\/([^?]+)(\??.*)$/.exec(str);
  if (!match || '/' == match[1]) {
    throw new Error('Invalid mongodb uri "' + str + '". Missing hostname');
  }

  var uris = match[1];
  var path = match[2];
  var db;

  uris.split(',').forEach(function (uri) {
    var o = parse(uri);

    if (o.host) {
      ret.hosts.push({
          host: o.host
        , port: parseInt(o.port, 10)
      })

      if (!db && o.db) {
        db = o.db;
      }
    } else if (o.ipc) {
      ret.hosts.push({ ipc: o.ipc });
    }

    if (o.auth) {
      ret.auth = {
          user: o.auth.user ? (o.auth.user) : void 0
        , pass: o.auth.pass ? (o.auth.pass) : void 0
      }
    }
  })

  if (!ret.hosts.length) {
    throw new Error('Invalid mongodb uri "' + str + '". Missing hostname');
  }

  var parts = path.split('?');

  if (!db) {
    if (parts[0]) {
      db = parts[0].replace(/^\//, '');
    } else {
      // deal with ipc formats
      db = /\/([^\.]+)$/.exec(match[1]);
      if (db && db[1]) {
        db = db[1];
      }
    }
  }

  if (db) {
    ret.db = db;
  } else if (ret.auth) {
    ret.db = ADMIN_DB;
  }

  if (parts[1]) {
    ret.options = options(parts[1]);
  }

  return ret;
}

/**
 * Parse str into key/val pairs casting values appropriately.
 */

function options (str) {
  var sep = /;/.test(str)
    ? ';'
    : '&';

  var ret = qs.parse(str, sep);

  Object.keys(ret).forEach(function (key) {
    var val = ret[key];
    if ('readPreferenceTags' == key) {
      val = readPref(val);
      if (val) {
        ret[key] = Array.isArray(val)
          ? val
          : [val];
      }
    } else {
      ret[key] = format(val);
    }
  });

  return ret;
}

function format (val) {
  var num;

  if ('true' == val) {
    return true;
  } else if ('false' == val) {
    return false;
  } else {
    num = parseInt(+val, 10);
    if (!isNaN(num)) {
      return num;
    }
  }

  return val;
}

function readPref (val) {
  var ret;

  if (Array.isArray(val)) {
    ret = val.map(readPref).filter(Boolean);
    return ret.length
      ? ret
      : undefined
  }

  var pair = val.split(',');
  var hasKeys;
  ret = {};

  pair.forEach(function (kv) {
    kv = (kv || '').trim();
    if (!kv) return;
    hasKeys = true;
    var split = kv.split(':');
    ret[split[0]] = format(split[1]);
  });

  return hasKeys && ret;
}

var ipcRgx = /\.sock/;

function parse (uriString) {
  // do not use require('url').parse b/c it can't handle # in username or pwd
  // mongo uris are strange

  var uri = uriString;
  var ret = {};
  var parts;
  var auth;
  var ipcs;

  // skip protocol
  uri = uri.replace(/^mongodb:\/\//, '');

  // auth
  if (/@/.test(uri)) {
    parts = uri.split(/@/);
    auth = parts[0];
    uri = parts[1];

    parts = auth.split(':');
    ret.auth = {};
    ret.auth.user = decodeURIComponent(parts[0]);
    ret.auth.pass = parts[1] ? decodeURIComponent(parts[1]) : void 0;
  }

  // unix domain sockets
  if (ipcRgx.test(uri)) {
    ipcs = uri.split(ipcRgx);
    ret.ipc = ipcs[0] + '.sock';

    // included a database?
    if (ipcs[1]) {
      // strip leading / from database name
      ipcs[1] = ipcs[1].replace(/^\//, '');

      if (ipcs[1]) {
        ret.db = ipcs[1];
      }
    }

    return ret;
  }

  // database name
  parts = uri.split('/');
  if (parts[1]) ret.db = parts[1];

  // ipv6, [ip]:host
  if (/^\[[^\]]+\]:\d+/.test(parts[0])) {
    ret.host = parts[0].substring(1, parts[0].indexOf(']:'));
    ret.port = parts[0].substring(parts[0].indexOf(']:') + ']:'.length);
  } else {
    // host:port
    parts = parts[0].split(':');
    ret.host = parts[0];
    ret.port = parts[1] || DEFAULT_PORT;
  }

  return ret;
}

/**
 * Version
 */

module.exports.version = require('../package.json').version;