Skip to content

Commit

Permalink
fix: connection string seed list parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
manarhusrieh committed Dec 9, 2024
1 parent 48ee112 commit f7c4ed3
Show file tree
Hide file tree
Showing 3 changed files with 352 additions and 19 deletions.
112 changes: 104 additions & 8 deletions lib/mongodb.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,15 +332,12 @@ MongoDB.prototype.connect = function(callback) {
if (callback) callback(err);
}

const urlObj = new URL(self.settings.url);

if ((urlObj.pathname === '' ||
urlObj.pathname.split('/')[1] === '') &&
typeof self.settings.database === 'string') {
urlObj.pathname = self.settings.database;
self.settings.url = urlObj.toString();
// This is special processing if database is not part of url, but is in settings
if (self.settings.url && self.settings.database) {
if (self.settings.url.indexOf('/' + self.settings.database) === -1) {
self.settings.url = processMongoDBURL(self.settings.database, self.settings.url);
}
}

new mongodb.MongoClient(self.settings.url, validOptions).connect(function(
err,
client,
Expand Down Expand Up @@ -2120,6 +2117,105 @@ MongoDB.prototype.rollback = function(tx, cb) {
});
};

exports.processMongoDBURL = processMongoDBURL;
/**
* This method parses a Mongo connection url string and refers the formats
* specified at: https://www.mongodb.com/docs/manual/reference/connection-string/.
* Since there are cases where database is not specified in the url, but as a settings property,
* the code has to reflect that in the url otherwise the MongoDB driver defaults to 'admin' db.
* @param {string} settingsDatabase - the database that will be added if url doesn't have a db specified
* @param {string} mongoUrl - the url to be processed for database manipulation
*/
function processMongoDBURL(settingsDatabase, mongoUrl) {
// Reference: https://www.mongodb.com/docs/manual/reference/connection-string/
// Standard format::: mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
// DNS SeedList format::: mongodb+srv://server.example.com/?connectTimeoutMS=300000&authSource=aDifferentAuthDB
// Actual replicaset example::: mongodb://mongodb1.example.com:27317,mongodb2.example.com:27017/?connectTimeoutMS=300000&replicaSet=mySet&authSource=aDifferentAuthDB

if (mongoUrl) {
// 1. Know the protocol
let baseUrl = '';
if (mongoUrl.startsWith('mongodb:'))
baseUrl = 'mongodb://';
else if (mongoUrl.startsWith('mongodb+srv:'))
baseUrl = 'mongodb+srv://';
else if (mongoUrl.startsWith('loopback-connector-mongodb:'))
baseUrl = 'loopback-connector-mongodb://';
else if (mongoUrl.startsWith('loopback-connector-mongodb+srv:'))
baseUrl = 'loopback-connector-mongodb+srv://';
else
return mongoUrl; // Not a MongoURL that we can process

let remainderUrl = mongoUrl.substring(baseUrl.length);
// 2. Check if userId/password is present
let uidPassword = '';
if (remainderUrl.indexOf('@') !== -1) {
const parts = remainderUrl.split('@');
uidPassword = parts[0];
if (parts.length === 2)
remainderUrl = parts[1];
else
remainderUrl = '';
}
let hosts = '';
let dbName = '';
let options = '';
let hostsArray = [];
// 3. Check if comma separated replicas are specified
if (remainderUrl.indexOf(',') !== -1) {
hostsArray = remainderUrl.split(',');
remainderUrl = hostsArray[hostsArray.length - 1];
}

// 4. Check if authDB is specified in the URL
const slashIndex = remainderUrl.indexOf('/');
if ((slashIndex !== -1)) {
if (slashIndex !== (remainderUrl.length - 1)) {
const optionsIndex = remainderUrl.indexOf('?');
if (optionsIndex !== -1) {
options = remainderUrl.substring(optionsIndex + 1);
dbName = remainderUrl.substring(slashIndex + 1, optionsIndex);
} else {
// No DB options specified
dbName = remainderUrl.substring(slashIndex + 1);
}
}

if (hostsArray.length > 1) {
const newHosts = hostsArray;
newHosts.pop();
newHosts.push(remainderUrl.substring(0, slashIndex));
hosts = newHosts.join(',');
} else {
hosts = remainderUrl.substring(0, slashIndex);
}
} else {
// No database specified
if (hostsArray.length > 1)
hosts = hostsArray.join(',');
else
hosts = remainderUrl;
}

// 5. Reconstruct url, but this time add database from settings if URL didn't have it
// The below code has an overlap with generateMongoDBURL()
let modifiedUrl = baseUrl;

if (uidPassword)
modifiedUrl += uidPassword + '@';
if (hosts)
modifiedUrl += hosts;

modifiedUrl += '/' + (dbName ? dbName : settingsDatabase);

if (options)
modifiedUrl += '?' + options;

return modifiedUrl;
}
return mongoUrl;
}

function isInTransation(options) {
const ops = {};
if (options && options.transaction && options.transaction.isInTransation)
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f7c4ed3

Please sign in to comment.