UNPKG

connection-string

Version:
334 lines (298 loc) 11.2 kB
(function (window) { 'use strict'; var invalidDefaults = 'Invalid \'defaults\' parameter!'; function ConnectionString(cs, defaults) { if (!(this instanceof ConnectionString)) { return new ConnectionString(cs, defaults); } if (typeof cs !== 'string') { throw new TypeError('Invalid connection string!'); } if (defaults !== undefined && defaults !== null && typeof defaults !== 'object') { throw new TypeError(invalidDefaults); } // Removing all trailing space symbols: cs = trim(cs); // Validating URL symbols: validateUrl(cs); // Extracting the protocol: var m = cs.match(/^[\w-_.+!*'()$%]*:\/\//); if (m) { var protocol = decode(m[0].replace(/:\/\//, '')); if (protocol) { this.protocol = protocol; } cs = cs.substr(m[0].length); } // Extracting user + password: m = cs.match(/^([\w-_.+!*'()$%]*):?([\w-_.+!*'()$%]*)@/); if (m) { if (m[1]) { this.user = decode(m[1]); } if (m[2]) { this.password = decode(m[2]); } cs = cs.substr(m[0].length); } // Extracting hosts details... // (if it starts with `/`, it is the first segment, so no hosts specified) if (cs[0] !== '/') { var endOfHosts = cs.search(/\/|\?/); var hosts = (endOfHosts === -1 ? cs : cs.substr(0, endOfHosts)).split(','); hosts.forEach(function (h) { var host = parseHost(h); if (host) { if (!this.hosts) { this.hosts = []; } this.hosts.push(host); } }, this); if (endOfHosts >= 0) { cs = cs.substr(endOfHosts); } } // Extracting segments: m = cs.match(/\/([\w-_.+!*'()$%]+)/g); if (m) { this.segments = m.map(function (s) { return decode(s.substr(1)); }); } // Extracting parameters: var idx = cs.indexOf('?'); if (idx !== -1) { cs = cs.substr(idx + 1); m = cs.match(/([\w-_.+!*'()$%]+)=([\w-_.+!*'()$%]+)/g); if (m) { var params = {}; m.forEach(function (s) { var a = s.split('='); var value = a[1].replace(/\+/g, ' '); params[decode(a[0])] = decode(value); }); this.params = params; } } if (defaults) { this.setDefaults(defaults); } } function validateUrl(url) { var idx = url.search(/[^A-Za-z0-9-._~:/?#[\]@!$&'()*+,;=%]/); if (idx >= 0) { var s = JSON.stringify(url[idx]).replace(/^"|"$/g, '\''); throw new Error('Invalid URL character ' + s + ' at position ' + idx); } } function parseHost(host, external) { if (external) { if (typeof host !== 'string') { throw new TypeError('Invalid \'host\' parameter!'); } host = trim(host); } var m, isIPv6 = false; if (host[0] === '[') { // This is IPv6, with [::] being the shortest possible m = host.match(/(\[([0-9a-z:%]{2,45})](?::(-?[0-9]+[^/?]*))?)/i); isIPv6 = true; } else { // It is either IPv4 or a name m = host.match(/(([a-z0-9%.$-]*)(?::(-?[0-9]+[^/?]*))?)/i); } if (m) { var h = {}; if (m[2]) { h.name = isIPv6 ? m[2] : decode(m[2]); h.isIPv6 = isIPv6; } if (m[3]) { var p = m[3], port = parseInt(p); if (port > 0 && port < 65536 && port.toString() === p) { h.port = port; } else { throw new Error('Invalid port: ' + p); } } if (h.name || h.port) { Object.defineProperty(h, 'toString', { value: function (options) { return fullHostName(h, options); } }); return h; } } return null; } function toString(options) { var s = ''; options = options || {}; if (this.protocol) { s += encode(this.protocol, options) + '://'; } if (this.user) { s += encode(this.user, options); if (this.password) { s += ':' + encode(this.password, options); } s += '@'; } else { if (this.password) { s += ':' + encode(this.password, options) + '@'; } } if (Array.isArray(this.hosts)) { s += this.hosts.map(function (h) { return fullHostName(h, options); }).join(); } if (Array.isArray(this.segments) && this.segments.length) { this.segments.forEach(function (seg) { s += '/' + encode(seg, options); }); } if (this.params) { var params = []; for (var a in this.params) { var value = this.params[a]; if (typeof value !== 'string') { value = JSON.stringify(value); } params.push(encode(a, options) + '=' + encode(value, options)); } if (params.length) { s += '?' + params.join('&'); } } return s; } function setDefaults(defaults) { if (!defaults || typeof defaults !== 'object') { throw new TypeError(invalidDefaults); } if (!('protocol' in this) && isText(defaults.protocol)) { this.protocol = trim(defaults.protocol); } // Missing default hosts are merged with the existing ones: if (Array.isArray(defaults.hosts)) { var hosts = Array.isArray(this.hosts) ? this.hosts : []; defaults.hosts.filter(function (d) { return d && typeof d === 'object'; }) .forEach(function (dh) { var found = false; for (var i = 0; i < hosts.length; i++) { var thisHost = fullHostName(hosts[i]), defHost = fullHostName(dh); if (equalStrings(thisHost, defHost)) { found = true; break; } } if (!found) { var obj = {}; if (isText(dh.name)) { obj.name = dh.name; obj.isIPv6 = !!dh.isIPv6; } var port = parseInt(dh.port); if (port > 0 && port < 65536) { obj.port = port; } if (obj.name || obj.port) { Object.defineProperty(obj, 'toString', { value: function (options) { return fullHostName(obj, options); } }); hosts.push(obj); } } }); if (hosts.length) { this.hosts = hosts; } } if (!('user' in this) && isText(defaults.user)) { this.user = trim(defaults.user); } if (!('password' in this) && isText(defaults.password)) { this.password = trim(defaults.password); } // Since the order of segments is usually important, we set default // segments as they are, but only when they are missing completely: if (!('segments' in this) && Array.isArray(defaults.segments)) { var s = defaults.segments.filter(isText); if (s.length) { this.segments = s; } } // Missing default params are merged with the existing ones: if (defaults.params && typeof defaults.params === 'object') { var keys = Object.keys(defaults.params); if (keys.length) { if (this.params && typeof(this.params) === 'object') { for (var a in defaults.params) { if (!(a in this.params)) { this.params[a] = defaults.params[a]; } } } else { this.params = {}; for (var b in defaults.params) { this.params[b] = defaults.params[b]; } } } } return this; } function fullHostName(obj, options) { options = options || {}; var a = ''; if (obj.name) { if (obj.isIPv6) { a = '[' + obj.name + ']'; } else { a = encode(obj.name, options); } } if (obj.port) { a += ':' + obj.port; } return a; } function encode(text, options) { text = encodeURIComponent(text); return options.encodeDollar ? text : text.replace(/%24/g, '$'); } function decode(text) { return decodeURIComponent(text); } function isText(txt) { return typeof txt === 'string' && /\S/.test(txt); } function trim(txt) { return txt.replace(/^[\s]*|[\s]*$/g, ''); } function equalStrings(str1, str2) { return (typeof str1 === 'string' && typeof str2 === 'string') && str1.toUpperCase() === str2.toUpperCase(); } Object.defineProperty(ConnectionString.prototype, 'toString', {value: toString}); Object.defineProperty(ConnectionString.prototype, 'setDefaults', {value: setDefaults}); Object.defineProperty(ConnectionString, 'parseHost', { value: function (host) { return parseHost(host, true); } }); ConnectionString.ConnectionString = ConnectionString; // To make it TypeScript-friendly /* istanbul ignore else */ if (typeof module === 'object' && module && typeof module.exports === 'object') { module.exports = ConnectionString; // Inside Node.js } else { window.ConnectionString = ConnectionString; // Inside a browser } })(this);