You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					638 lines
				
				21 KiB
			
		
		
			
		
	
	
					638 lines
				
				21 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const ReadPreference = require('./core').ReadPreference;
							 | 
						||
| 
								 | 
							
								const parser = require('url');
							 | 
						||
| 
								 | 
							
								const f = require('util').format;
							 | 
						||
| 
								 | 
							
								const Logger = require('./core').Logger;
							 | 
						||
| 
								 | 
							
								const dns = require('dns');
							 | 
						||
| 
								 | 
							
								const ReadConcern = require('./read_concern');
							 | 
						||
| 
								 | 
							
								const qs = require('querystring');
							 | 
						||
| 
								 | 
							
								const MongoParseError = require('./core/error').MongoParseError;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = function(url, options, callback) {
							 | 
						||
| 
								 | 
							
								  if (typeof options === 'function') (callback = options), (options = {});
							 | 
						||
| 
								 | 
							
								  options = options || {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let result;
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    result = parser.parse(url, true);
							 | 
						||
| 
								 | 
							
								  } catch (e) {
							 | 
						||
| 
								 | 
							
								    return callback(new Error('URL malformed, cannot be parsed'));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (result.protocol !== 'mongodb:' && result.protocol !== 'mongodb+srv:') {
							 | 
						||
| 
								 | 
							
								    return callback(new Error('Invalid schema, expected `mongodb` or `mongodb+srv`'));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (result.protocol === 'mongodb:') {
							 | 
						||
| 
								 | 
							
								    return parseHandler(url, options, callback);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Otherwise parse this as an SRV record
							 | 
						||
| 
								 | 
							
								  if (result.hostname.split('.').length < 3) {
							 | 
						||
| 
								 | 
							
								    return callback(new Error('URI does not have hostname, domain name and tld'));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  result.domainLength = result.hostname.split('.').length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const hostname = url.substring('mongodb+srv://'.length).split('/')[0];
							 | 
						||
| 
								 | 
							
								  if (hostname.match(',')) {
							 | 
						||
| 
								 | 
							
								    return callback(new Error('Invalid URI, cannot contain multiple hostnames'));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (result.port) {
							 | 
						||
| 
								 | 
							
								    return callback(new Error('Ports not accepted with `mongodb+srv` URIs'));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let srvAddress = `_mongodb._tcp.${result.host}`;
							 | 
						||
| 
								 | 
							
								  dns.resolveSrv(srvAddress, function(err, addresses) {
							 | 
						||
| 
								 | 
							
								    if (err) return callback(err);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (addresses.length === 0) {
							 | 
						||
| 
								 | 
							
								      return callback(new Error('No addresses found at host'));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for (let i = 0; i < addresses.length; i++) {
							 | 
						||
| 
								 | 
							
								      if (!matchesParentDomain(addresses[i].name, result.hostname, result.domainLength)) {
							 | 
						||
| 
								 | 
							
								        return callback(new Error('Server record does not share hostname with parent URI'));
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let base = result.auth ? `mongodb://${result.auth}@` : `mongodb://`;
							 | 
						||
| 
								 | 
							
								    let connectionStrings = addresses.map(function(address, i) {
							 | 
						||
| 
								 | 
							
								      if (i === 0) return `${base}${address.name}:${address.port}`;
							 | 
						||
| 
								 | 
							
								      else return `${address.name}:${address.port}`;
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let connectionString = connectionStrings.join(',') + '/';
							 | 
						||
| 
								 | 
							
								    let connectionStringOptions = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Add the default database if needed
							 | 
						||
| 
								 | 
							
								    if (result.path) {
							 | 
						||
| 
								 | 
							
								      let defaultDb = result.path.slice(1);
							 | 
						||
| 
								 | 
							
								      if (defaultDb.indexOf('?') !== -1) {
							 | 
						||
| 
								 | 
							
								        defaultDb = defaultDb.slice(0, defaultDb.indexOf('?'));
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      connectionString += defaultDb;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Default to SSL true
							 | 
						||
| 
								 | 
							
								    if (!options.ssl && !result.search) {
							 | 
						||
| 
								 | 
							
								      connectionStringOptions.push('ssl=true');
							 | 
						||
| 
								 | 
							
								    } else if (!options.ssl && result.search && !result.search.match('ssl')) {
							 | 
						||
| 
								 | 
							
								      connectionStringOptions.push('ssl=true');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Keep original uri options
							 | 
						||
| 
								 | 
							
								    if (result.search) {
							 | 
						||
| 
								 | 
							
								      connectionStringOptions.push(result.search.replace('?', ''));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    dns.resolveTxt(result.host, function(err, record) {
							 | 
						||
| 
								 | 
							
								      if (err && err.code !== 'ENODATA' && err.code !== 'ENOTFOUND') return callback(err);
							 | 
						||
| 
								 | 
							
								      if (err && err.code === 'ENODATA') record = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (record) {
							 | 
						||
| 
								 | 
							
								        if (record.length > 1) {
							 | 
						||
| 
								 | 
							
								          return callback(new MongoParseError('Multiple text records not allowed'));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        record = record[0].join('');
							 | 
						||
| 
								 | 
							
								        const parsedRecord = qs.parse(record);
							 | 
						||
| 
								 | 
							
								        const items = Object.keys(parsedRecord);
							 | 
						||
| 
								 | 
							
								        if (Object.keys(items).some(k => k.toLowerCase() === 'loadbalanced')) {
							 | 
						||
| 
								 | 
							
								          return callback(new MongoParseError('Load balancer mode requires driver version 4+'));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (items.some(item => item !== 'authSource' && item !== 'replicaSet')) {
							 | 
						||
| 
								 | 
							
								          return callback(
							 | 
						||
| 
								 | 
							
								            new MongoParseError('Text record must only set `authSource` or `replicaSet`')
							 | 
						||
| 
								 | 
							
								          );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (items.length > 0) {
							 | 
						||
| 
								 | 
							
								          connectionStringOptions.push(record);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Add any options to the connection string
							 | 
						||
| 
								 | 
							
								      if (connectionStringOptions.length) {
							 | 
						||
| 
								 | 
							
								        connectionString += `?${connectionStringOptions.join('&')}`;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      parseHandler(connectionString, options, callback);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function matchesParentDomain(srvAddress, parentDomain) {
							 | 
						||
| 
								 | 
							
								  let regex = /^.*?\./;
							 | 
						||
| 
								 | 
							
								  let srv = `.${srvAddress.replace(regex, '')}`;
							 | 
						||
| 
								 | 
							
								  let parent = `.${parentDomain.replace(regex, '')}`;
							 | 
						||
| 
								 | 
							
								  if (srv.endsWith(parent)) return true;
							 | 
						||
| 
								 | 
							
								  else return false;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseHandler(address, options, callback) {
							 | 
						||
| 
								 | 
							
								  let result, err;
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    result = parseConnectionString(address, options);
							 | 
						||
| 
								 | 
							
								  } catch (e) {
							 | 
						||
| 
								 | 
							
								    err = e;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return err ? callback(err, null) : callback(null, result);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function parseConnectionString(url, options) {
							 | 
						||
| 
								 | 
							
								  // Variables
							 | 
						||
| 
								 | 
							
								  let connection_part = '';
							 | 
						||
| 
								 | 
							
								  let auth_part = '';
							 | 
						||
| 
								 | 
							
								  let query_string_part = '';
							 | 
						||
| 
								 | 
							
								  let dbName = 'admin';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Url parser result
							 | 
						||
| 
								 | 
							
								  let result = parser.parse(url, true);
							 | 
						||
| 
								 | 
							
								  if ((result.hostname == null || result.hostname === '') && url.indexOf('.sock') === -1) {
							 | 
						||
| 
								 | 
							
								    throw new Error('No hostname or hostnames provided in connection string');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (result.port === '0') {
							 | 
						||
| 
								 | 
							
								    throw new Error('Invalid port (zero) with hostname');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!isNaN(parseInt(result.port, 10)) && parseInt(result.port, 10) > 65535) {
							 | 
						||
| 
								 | 
							
								    throw new Error('Invalid port (larger than 65535) with hostname');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    result.path &&
							 | 
						||
| 
								 | 
							
								    result.path.length > 0 &&
							 | 
						||
| 
								 | 
							
								    result.path[0] !== '/' &&
							 | 
						||
| 
								 | 
							
								    url.indexOf('.sock') === -1
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    throw new Error('Missing delimiting slash between hosts and options');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (result.query) {
							 | 
						||
| 
								 | 
							
								    for (let name in result.query) {
							 | 
						||
| 
								 | 
							
								      if (name.indexOf('::') !== -1) {
							 | 
						||
| 
								 | 
							
								        throw new Error('Double colon in host identifier');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (result.query[name] === '') {
							 | 
						||
| 
								 | 
							
								        throw new Error('Query parameter ' + name + ' is an incomplete value pair');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (result.auth) {
							 | 
						||
| 
								 | 
							
								    let parts = result.auth.split(':');
							 | 
						||
| 
								 | 
							
								    if (url.indexOf(result.auth) !== -1 && parts.length > 2) {
							 | 
						||
| 
								 | 
							
								      throw new Error('Username with password containing an unescaped colon');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (url.indexOf(result.auth) !== -1 && result.auth.indexOf('@') !== -1) {
							 | 
						||
| 
								 | 
							
								      throw new Error('Username containing an unescaped at-sign');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Remove query
							 | 
						||
| 
								 | 
							
								  let clean = url.split('?').shift();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Extract the list of hosts
							 | 
						||
| 
								 | 
							
								  let strings = clean.split(',');
							 | 
						||
| 
								 | 
							
								  let hosts = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (let i = 0; i < strings.length; i++) {
							 | 
						||
| 
								 | 
							
								    let hostString = strings[i];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (hostString.indexOf('mongodb') !== -1) {
							 | 
						||
| 
								 | 
							
								      if (hostString.indexOf('@') !== -1) {
							 | 
						||
| 
								 | 
							
								        hosts.push(hostString.split('@').pop());
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        hosts.push(hostString.substr('mongodb://'.length));
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else if (hostString.indexOf('/') !== -1) {
							 | 
						||
| 
								 | 
							
								      hosts.push(hostString.split('/').shift());
							 | 
						||
| 
								 | 
							
								    } else if (hostString.indexOf('/') === -1) {
							 | 
						||
| 
								 | 
							
								      hosts.push(hostString.trim());
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (let i = 0; i < hosts.length; i++) {
							 | 
						||
| 
								 | 
							
								    let r = parser.parse(f('mongodb://%s', hosts[i].trim()));
							 | 
						||
| 
								 | 
							
								    if (r.path && r.path.indexOf('.sock') !== -1) continue;
							 | 
						||
| 
								 | 
							
								    if (r.path && r.path.indexOf(':') !== -1) {
							 | 
						||
| 
								 | 
							
								      // Not connecting to a socket so check for an extra slash in the hostname.
							 | 
						||
| 
								 | 
							
								      // Using String#split as perf is better than match.
							 | 
						||
| 
								 | 
							
								      if (r.path.split('/').length > 1 && r.path.indexOf('::') === -1) {
							 | 
						||
| 
								 | 
							
								        throw new Error('Slash in host identifier');
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        throw new Error('Double colon in host identifier');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // If we have a ? mark cut the query elements off
							 | 
						||
| 
								 | 
							
								  if (url.indexOf('?') !== -1) {
							 | 
						||
| 
								 | 
							
								    query_string_part = url.substr(url.indexOf('?') + 1);
							 | 
						||
| 
								 | 
							
								    connection_part = url.substring('mongodb://'.length, url.indexOf('?'));
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    connection_part = url.substring('mongodb://'.length);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Check if we have auth params
							 | 
						||
| 
								 | 
							
								  if (connection_part.indexOf('@') !== -1) {
							 | 
						||
| 
								 | 
							
								    auth_part = connection_part.split('@')[0];
							 | 
						||
| 
								 | 
							
								    connection_part = connection_part.split('@')[1];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Check there is not more than one unescaped slash
							 | 
						||
| 
								 | 
							
								  if (connection_part.split('/').length > 2) {
							 | 
						||
| 
								 | 
							
								    throw new Error(
							 | 
						||
| 
								 | 
							
								      "Unsupported host '" +
							 | 
						||
| 
								 | 
							
								        connection_part.split('?')[0] +
							 | 
						||
| 
								 | 
							
								        "', hosts must be URL encoded and contain at most one unencoded slash"
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Check if the connection string has a db
							 | 
						||
| 
								 | 
							
								  if (connection_part.indexOf('.sock') !== -1) {
							 | 
						||
| 
								 | 
							
								    if (connection_part.indexOf('.sock/') !== -1) {
							 | 
						||
| 
								 | 
							
								      dbName = connection_part.split('.sock/')[1];
							 | 
						||
| 
								 | 
							
								      // Check if multiple database names provided, or just an illegal trailing backslash
							 | 
						||
| 
								 | 
							
								      if (dbName.indexOf('/') !== -1) {
							 | 
						||
| 
								 | 
							
								        if (dbName.split('/').length === 2 && dbName.split('/')[1].length === 0) {
							 | 
						||
| 
								 | 
							
								          throw new Error('Illegal trailing backslash after database name');
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        throw new Error('More than 1 database name in URL');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      connection_part = connection_part.split(
							 | 
						||
| 
								 | 
							
								        '/',
							 | 
						||
| 
								 | 
							
								        connection_part.indexOf('.sock') + '.sock'.length
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } else if (connection_part.indexOf('/') !== -1) {
							 | 
						||
| 
								 | 
							
								    // Check if multiple database names provided, or just an illegal trailing backslash
							 | 
						||
| 
								 | 
							
								    if (connection_part.split('/').length > 2) {
							 | 
						||
| 
								 | 
							
								      if (connection_part.split('/')[2].length === 0) {
							 | 
						||
| 
								 | 
							
								        throw new Error('Illegal trailing backslash after database name');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      throw new Error('More than 1 database name in URL');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    dbName = connection_part.split('/')[1];
							 | 
						||
| 
								 | 
							
								    connection_part = connection_part.split('/')[0];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // URI decode the host information
							 | 
						||
| 
								 | 
							
								  connection_part = decodeURIComponent(connection_part);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Result object
							 | 
						||
| 
								 | 
							
								  let object = {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Pick apart the authentication part of the string
							 | 
						||
| 
								 | 
							
								  let authPart = auth_part || '';
							 | 
						||
| 
								 | 
							
								  let auth = authPart.split(':', 2);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Decode the authentication URI components and verify integrity
							 | 
						||
| 
								 | 
							
								  let user = decodeURIComponent(auth[0]);
							 | 
						||
| 
								 | 
							
								  if (auth[0] !== encodeURIComponent(user)) {
							 | 
						||
| 
								 | 
							
								    throw new Error('Username contains an illegal unescaped character');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  auth[0] = user;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (auth[1]) {
							 | 
						||
| 
								 | 
							
								    let pass = decodeURIComponent(auth[1]);
							 | 
						||
| 
								 | 
							
								    if (auth[1] !== encodeURIComponent(pass)) {
							 | 
						||
| 
								 | 
							
								      throw new Error('Password contains an illegal unescaped character');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    auth[1] = pass;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Add auth to final object if we have 2 elements
							 | 
						||
| 
								 | 
							
								  if (auth.length === 2) object.auth = { user: auth[0], password: auth[1] };
							 | 
						||
| 
								 | 
							
								  // if user provided auth options, use that
							 | 
						||
| 
								 | 
							
								  if (options && options.auth != null) object.auth = options.auth;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Variables used for temporary storage
							 | 
						||
| 
								 | 
							
								  let hostPart;
							 | 
						||
| 
								 | 
							
								  let urlOptions;
							 | 
						||
| 
								 | 
							
								  let servers;
							 | 
						||
| 
								 | 
							
								  let compression;
							 | 
						||
| 
								 | 
							
								  let serverOptions = { socketOptions: {} };
							 | 
						||
| 
								 | 
							
								  let dbOptions = { read_preference_tags: [] };
							 | 
						||
| 
								 | 
							
								  let replSetServersOptions = { socketOptions: {} };
							 | 
						||
| 
								 | 
							
								  let mongosOptions = { socketOptions: {} };
							 | 
						||
| 
								 | 
							
								  // Add server options to final object
							 | 
						||
| 
								 | 
							
								  object.server_options = serverOptions;
							 | 
						||
| 
								 | 
							
								  object.db_options = dbOptions;
							 | 
						||
| 
								 | 
							
								  object.rs_options = replSetServersOptions;
							 | 
						||
| 
								 | 
							
								  object.mongos_options = mongosOptions;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Let's check if we are using a domain socket
							 | 
						||
| 
								 | 
							
								  if (url.match(/\.sock/)) {
							 | 
						||
| 
								 | 
							
								    // Split out the socket part
							 | 
						||
| 
								 | 
							
								    let domainSocket = url.substring(
							 | 
						||
| 
								 | 
							
								      url.indexOf('mongodb://') + 'mongodb://'.length,
							 | 
						||
| 
								 | 
							
								      url.lastIndexOf('.sock') + '.sock'.length
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								    // Clean out any auth stuff if any
							 | 
						||
| 
								 | 
							
								    if (domainSocket.indexOf('@') !== -1) domainSocket = domainSocket.split('@')[1];
							 | 
						||
| 
								 | 
							
								    domainSocket = decodeURIComponent(domainSocket);
							 | 
						||
| 
								 | 
							
								    servers = [{ domain_socket: domainSocket }];
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    // Split up the db
							 | 
						||
| 
								 | 
							
								    hostPart = connection_part;
							 | 
						||
| 
								 | 
							
								    // Deduplicate servers
							 | 
						||
| 
								 | 
							
								    let deduplicatedServers = {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Parse all server results
							 | 
						||
| 
								 | 
							
								    servers = hostPart
							 | 
						||
| 
								 | 
							
								      .split(',')
							 | 
						||
| 
								 | 
							
								      .map(function(h) {
							 | 
						||
| 
								 | 
							
								        let _host, _port, ipv6match;
							 | 
						||
| 
								 | 
							
								        //check if it matches [IPv6]:port, where the port number is optional
							 | 
						||
| 
								 | 
							
								        if ((ipv6match = /\[([^\]]+)\](?::(.+))?/.exec(h))) {
							 | 
						||
| 
								 | 
							
								          _host = ipv6match[1];
							 | 
						||
| 
								 | 
							
								          _port = parseInt(ipv6match[2], 10) || 27017;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          //otherwise assume it's IPv4, or plain hostname
							 | 
						||
| 
								 | 
							
								          let hostPort = h.split(':', 2);
							 | 
						||
| 
								 | 
							
								          _host = hostPort[0] || 'localhost';
							 | 
						||
| 
								 | 
							
								          _port = hostPort[1] != null ? parseInt(hostPort[1], 10) : 27017;
							 | 
						||
| 
								 | 
							
								          // Check for localhost?safe=true style case
							 | 
						||
| 
								 | 
							
								          if (_host.indexOf('?') !== -1) _host = _host.split(/\?/)[0];
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // No entry returned for duplicate server
							 | 
						||
| 
								 | 
							
								        if (deduplicatedServers[_host + '_' + _port]) return null;
							 | 
						||
| 
								 | 
							
								        deduplicatedServers[_host + '_' + _port] = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Return the mapped object
							 | 
						||
| 
								 | 
							
								        return { host: _host, port: _port };
							 | 
						||
| 
								 | 
							
								      })
							 | 
						||
| 
								 | 
							
								      .filter(function(x) {
							 | 
						||
| 
								 | 
							
								        return x != null;
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Get the db name
							 | 
						||
| 
								 | 
							
								  object.dbName = dbName || 'admin';
							 | 
						||
| 
								 | 
							
								  // Split up all the options
							 | 
						||
| 
								 | 
							
								  urlOptions = (query_string_part || '').split(/[&;]/);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (urlOptions.some(k => k.toLowerCase() === 'loadbalanced')) {
							 | 
						||
| 
								 | 
							
								    throw new MongoParseError('Load balancer mode requires driver version 4+');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Ugh, we have to figure out which options go to which constructor manually.
							 | 
						||
| 
								 | 
							
								  urlOptions.forEach(function(opt) {
							 | 
						||
| 
								 | 
							
								    if (!opt) return;
							 | 
						||
| 
								 | 
							
								    var splitOpt = opt.split('='),
							 | 
						||
| 
								 | 
							
								      name = splitOpt[0],
							 | 
						||
| 
								 | 
							
								      value = splitOpt[1];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Options implementations
							 | 
						||
| 
								 | 
							
								    switch (name) {
							 | 
						||
| 
								 | 
							
								      case 'slaveOk':
							 | 
						||
| 
								 | 
							
								      case 'slave_ok':
							 | 
						||
| 
								 | 
							
								        serverOptions.slave_ok = value === 'true';
							 | 
						||
| 
								 | 
							
								        dbOptions.slaveOk = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'maxPoolSize':
							 | 
						||
| 
								 | 
							
								      case 'poolSize':
							 | 
						||
| 
								 | 
							
								        serverOptions.poolSize = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.poolSize = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'appname':
							 | 
						||
| 
								 | 
							
								        object.appname = decodeURIComponent(value);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'autoReconnect':
							 | 
						||
| 
								 | 
							
								      case 'auto_reconnect':
							 | 
						||
| 
								 | 
							
								        serverOptions.auto_reconnect = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'ssl':
							 | 
						||
| 
								 | 
							
								        if (value === 'prefer') {
							 | 
						||
| 
								 | 
							
								          serverOptions.ssl = value;
							 | 
						||
| 
								 | 
							
								          replSetServersOptions.ssl = value;
							 | 
						||
| 
								 | 
							
								          mongosOptions.ssl = value;
							 | 
						||
| 
								 | 
							
								          break;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        serverOptions.ssl = value === 'true';
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.ssl = value === 'true';
							 | 
						||
| 
								 | 
							
								        mongosOptions.ssl = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'sslValidate':
							 | 
						||
| 
								 | 
							
								        serverOptions.sslValidate = value === 'true';
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.sslValidate = value === 'true';
							 | 
						||
| 
								 | 
							
								        mongosOptions.sslValidate = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'replicaSet':
							 | 
						||
| 
								 | 
							
								      case 'rs_name':
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.rs_name = value;
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'reconnectWait':
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.reconnectWait = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'retries':
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.retries = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'readSecondary':
							 | 
						||
| 
								 | 
							
								      case 'read_secondary':
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.read_secondary = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'fsync':
							 | 
						||
| 
								 | 
							
								        dbOptions.fsync = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'journal':
							 | 
						||
| 
								 | 
							
								        dbOptions.j = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'safe':
							 | 
						||
| 
								 | 
							
								        dbOptions.safe = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'nativeParser':
							 | 
						||
| 
								 | 
							
								      case 'native_parser':
							 | 
						||
| 
								 | 
							
								        dbOptions.native_parser = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'readConcernLevel':
							 | 
						||
| 
								 | 
							
								        dbOptions.readConcern = new ReadConcern(value);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'connectTimeoutMS':
							 | 
						||
| 
								 | 
							
								        serverOptions.socketOptions.connectTimeoutMS = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.socketOptions.connectTimeoutMS = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        mongosOptions.socketOptions.connectTimeoutMS = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'socketTimeoutMS':
							 | 
						||
| 
								 | 
							
								        serverOptions.socketOptions.socketTimeoutMS = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        replSetServersOptions.socketOptions.socketTimeoutMS = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        mongosOptions.socketOptions.socketTimeoutMS = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'w':
							 | 
						||
| 
								 | 
							
								        dbOptions.w = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        if (isNaN(dbOptions.w)) dbOptions.w = value;
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'authSource':
							 | 
						||
| 
								 | 
							
								        dbOptions.authSource = value;
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'gssapiServiceName':
							 | 
						||
| 
								 | 
							
								        dbOptions.gssapiServiceName = value;
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'authMechanism':
							 | 
						||
| 
								 | 
							
								        if (value === 'GSSAPI') {
							 | 
						||
| 
								 | 
							
								          // If no password provided decode only the principal
							 | 
						||
| 
								 | 
							
								          if (object.auth == null) {
							 | 
						||
| 
								 | 
							
								            let urlDecodeAuthPart = decodeURIComponent(authPart);
							 | 
						||
| 
								 | 
							
								            if (urlDecodeAuthPart.indexOf('@') === -1)
							 | 
						||
| 
								 | 
							
								              throw new Error('GSSAPI requires a provided principal');
							 | 
						||
| 
								 | 
							
								            object.auth = { user: urlDecodeAuthPart, password: null };
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            object.auth.user = decodeURIComponent(object.auth.user);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        } else if (value === 'MONGODB-X509') {
							 | 
						||
| 
								 | 
							
								          object.auth = { user: decodeURIComponent(authPart) };
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Only support GSSAPI or MONGODB-CR for now
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								          value !== 'GSSAPI' &&
							 | 
						||
| 
								 | 
							
								          value !== 'MONGODB-X509' &&
							 | 
						||
| 
								 | 
							
								          value !== 'MONGODB-CR' &&
							 | 
						||
| 
								 | 
							
								          value !== 'DEFAULT' &&
							 | 
						||
| 
								 | 
							
								          value !== 'SCRAM-SHA-1' &&
							 | 
						||
| 
								 | 
							
								          value !== 'SCRAM-SHA-256' &&
							 | 
						||
| 
								 | 
							
								          value !== 'PLAIN'
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								          throw new Error(
							 | 
						||
| 
								 | 
							
								            'Only DEFAULT, GSSAPI, PLAIN, MONGODB-X509, or SCRAM-SHA-1 is supported by authMechanism'
							 | 
						||
| 
								 | 
							
								          );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Authentication mechanism
							 | 
						||
| 
								 | 
							
								        dbOptions.authMechanism = value;
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'authMechanismProperties':
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          // Split up into key, value pairs
							 | 
						||
| 
								 | 
							
								          let values = value.split(',');
							 | 
						||
| 
								 | 
							
								          let o = {};
							 | 
						||
| 
								 | 
							
								          // For each value split into key, value
							 | 
						||
| 
								 | 
							
								          values.forEach(function(x) {
							 | 
						||
| 
								 | 
							
								            let v = x.split(':');
							 | 
						||
| 
								 | 
							
								            o[v[0]] = v[1];
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          // Set all authMechanismProperties
							 | 
						||
| 
								 | 
							
								          dbOptions.authMechanismProperties = o;
							 | 
						||
| 
								 | 
							
								          // Set the service name value
							 | 
						||
| 
								 | 
							
								          if (typeof o.SERVICE_NAME === 'string') dbOptions.gssapiServiceName = o.SERVICE_NAME;
							 | 
						||
| 
								 | 
							
								          if (typeof o.SERVICE_REALM === 'string') dbOptions.gssapiServiceRealm = o.SERVICE_REALM;
							 | 
						||
| 
								 | 
							
								          if (typeof o.CANONICALIZE_HOST_NAME === 'string')
							 | 
						||
| 
								 | 
							
								            dbOptions.gssapiCanonicalizeHostName =
							 | 
						||
| 
								 | 
							
								              o.CANONICALIZE_HOST_NAME === 'true' ? true : false;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'wtimeoutMS':
							 | 
						||
| 
								 | 
							
								        dbOptions.wtimeout = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'readPreference':
							 | 
						||
| 
								 | 
							
								        if (!ReadPreference.isValid(value))
							 | 
						||
| 
								 | 
							
								          throw new Error(
							 | 
						||
| 
								 | 
							
								            'readPreference must be either primary/primaryPreferred/secondary/secondaryPreferred/nearest'
							 | 
						||
| 
								 | 
							
								          );
							 | 
						||
| 
								 | 
							
								        dbOptions.readPreference = value;
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'maxStalenessSeconds':
							 | 
						||
| 
								 | 
							
								        dbOptions.maxStalenessSeconds = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'readPreferenceTags':
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          // Decode the value
							 | 
						||
| 
								 | 
							
								          value = decodeURIComponent(value);
							 | 
						||
| 
								 | 
							
								          // Contains the tag object
							 | 
						||
| 
								 | 
							
								          let tagObject = {};
							 | 
						||
| 
								 | 
							
								          if (value == null || value === '') {
							 | 
						||
| 
								 | 
							
								            dbOptions.read_preference_tags.push(tagObject);
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          // Split up the tags
							 | 
						||
| 
								 | 
							
								          let tags = value.split(/,/);
							 | 
						||
| 
								 | 
							
								          for (let i = 0; i < tags.length; i++) {
							 | 
						||
| 
								 | 
							
								            let parts = tags[i].trim().split(/:/);
							 | 
						||
| 
								 | 
							
								            tagObject[parts[0]] = parts[1];
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          // Set the preferences tags
							 | 
						||
| 
								 | 
							
								          dbOptions.read_preference_tags.push(tagObject);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'compressors':
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          compression = serverOptions.compression || {};
							 | 
						||
| 
								 | 
							
								          let compressors = value.split(',');
							 | 
						||
| 
								 | 
							
								          if (
							 | 
						||
| 
								 | 
							
								            !compressors.every(function(compressor) {
							 | 
						||
| 
								 | 
							
								              return compressor === 'snappy' || compressor === 'zlib';
							 | 
						||
| 
								 | 
							
								            })
							 | 
						||
| 
								 | 
							
								          ) {
							 | 
						||
| 
								 | 
							
								            throw new Error('Compressors must be at least one of snappy or zlib');
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          compression.compressors = compressors;
							 | 
						||
| 
								 | 
							
								          serverOptions.compression = compression;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'zlibCompressionLevel':
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          compression = serverOptions.compression || {};
							 | 
						||
| 
								 | 
							
								          let zlibCompressionLevel = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								          if (zlibCompressionLevel < -1 || zlibCompressionLevel > 9) {
							 | 
						||
| 
								 | 
							
								            throw new Error('zlibCompressionLevel must be an integer between -1 and 9');
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          compression.zlibCompressionLevel = zlibCompressionLevel;
							 | 
						||
| 
								 | 
							
								          serverOptions.compression = compression;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'retryWrites':
							 | 
						||
| 
								 | 
							
								        dbOptions.retryWrites = value === 'true';
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      case 'minSize':
							 | 
						||
| 
								 | 
							
								        dbOptions.minSize = parseInt(value, 10);
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      default:
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          let logger = Logger('URL Parser');
							 | 
						||
| 
								 | 
							
								          logger.warn(`${name} is not supported as a connection string option`);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // No tags: should be null (not [])
							 | 
						||
| 
								 | 
							
								  if (dbOptions.read_preference_tags.length === 0) {
							 | 
						||
| 
								 | 
							
								    dbOptions.read_preference_tags = null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Validate if there are an invalid write concern combinations
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    (dbOptions.w === -1 || dbOptions.w === 0) &&
							 | 
						||
| 
								 | 
							
								    (dbOptions.journal === true || dbOptions.fsync === true || dbOptions.safe === true)
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								    throw new Error('w set to -1 or 0 cannot be combined with safe/w/journal/fsync');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // If no read preference set it to primary
							 | 
						||
| 
								 | 
							
								  if (!dbOptions.readPreference) {
							 | 
						||
| 
								 | 
							
								    dbOptions.readPreference = 'primary';
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // make sure that user-provided options are applied with priority
							 | 
						||
| 
								 | 
							
								  dbOptions = Object.assign(dbOptions, options);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Add servers to result
							 | 
						||
| 
								 | 
							
								  object.servers = servers;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Returned parsed object
							 | 
						||
| 
								 | 
							
								  return object;
							 | 
						||
| 
								 | 
							
								}
							 |