Skip to content

Commit a955e3d

Browse files
committed
fix: connection string seed list parsing
closes: #663 Signed-off-by: jaishirole <[email protected]>
1 parent 92ae33c commit a955e3d

File tree

3 files changed

+440
-77
lines changed

3 files changed

+440
-77
lines changed

lib/mongodb.js

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,11 @@ MongoDB.prototype.connect = function(callback) {
332332
if (callback) callback(err);
333333
}
334334

335-
const urlObj = new URL(self.settings.url);
336-
337-
if ((urlObj.pathname === '' ||
338-
urlObj.pathname.split('/')[1] === '') &&
339-
typeof self.settings.database === 'string') {
340-
urlObj.pathname = self.settings.database;
341-
self.settings.url = urlObj.toString();
335+
// This is special processing if database is not part of url, but is in settings
336+
if (self.settings.url && self.settings.database) {
337+
if (self.settings.url.indexOf('/' + self.settings.database) === -1) {
338+
self.settings.url = processMongoDBURL(self.settings.database, self.settings.url);
339+
}
342340
}
343341

344342
new mongodb.MongoClient(self.settings.url, validOptions).connect(function(
@@ -2118,6 +2116,105 @@ MongoDB.prototype.rollback = function(tx, cb) {
21182116
});
21192117
};
21202118

2119+
exports.processMongoDBURL = processMongoDBURL;
2120+
/**
2121+
* This method parses a Mongo connection url string and refers the formats
2122+
* specified at: https://www.mongodb.com/docs/manual/reference/connection-string/.
2123+
* Since there are cases where database is not specified in the url, but as a settings property,
2124+
* the code has to reflect that in the url otherwise the MongoDB driver defaults to 'admin' db.
2125+
* @param {string} settingsDatabase - the database that will be added if url doesn't have a db specified
2126+
* @param {string} mongoUrl - the url to be processed for database manipulation
2127+
*/
2128+
function processMongoDBURL(settingsDatabase, mongoUrl) {
2129+
// Reference: https://www.mongodb.com/docs/manual/reference/connection-string/
2130+
// Standard format::: mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
2131+
// DNS SeedList format::: mongodb+srv://server.example.com/?connectTimeoutMS=300000&authSource=aDifferentAuthDB
2132+
// Actual replicaset example::: mongodb://mongodb1.example.com:27317,mongodb2.example.com:27017/?connectTimeoutMS=300000&replicaSet=mySet&authSource=aDifferentAuthDB
2133+
2134+
if (mongoUrl) {
2135+
// 1. Know the protocol
2136+
let baseUrl = '';
2137+
if (mongoUrl.startsWith('mongodb:'))
2138+
baseUrl = 'mongodb://';
2139+
else if (mongoUrl.startsWith('mongodb+srv:'))
2140+
baseUrl = 'mongodb+srv://';
2141+
else if (mongoUrl.startsWith('loopback-connector-mongodb:'))
2142+
baseUrl = 'loopback-connector-mongodb://';
2143+
else if (mongoUrl.startsWith('loopback-connector-mongodb+srv:'))
2144+
baseUrl = 'loopback-connector-mongodb+srv://';
2145+
else
2146+
return mongoUrl; // Not a MongoURL that we can process
2147+
2148+
let remainderUrl = mongoUrl.substring(baseUrl.length);
2149+
// 2. Check if userId/password is present
2150+
let uidPassword = '';
2151+
if (remainderUrl.indexOf('@') !== -1) {
2152+
const parts = remainderUrl.split('@');
2153+
uidPassword = parts[0];
2154+
if (parts.length === 2)
2155+
remainderUrl = parts[1];
2156+
else
2157+
remainderUrl = '';
2158+
}
2159+
let hosts = '';
2160+
let dbName = '';
2161+
let options = '';
2162+
let hostsArray = [];
2163+
// 3. Check if comma separated replicas are specified
2164+
if (remainderUrl.indexOf(',') !== -1) {
2165+
hostsArray = remainderUrl.split(',');
2166+
remainderUrl = hostsArray[hostsArray.length - 1];
2167+
}
2168+
2169+
// 4. Check if authDB is specified in the URL
2170+
const slashIndex = remainderUrl.indexOf('/');
2171+
if ((slashIndex !== -1)) {
2172+
if (slashIndex !== (remainderUrl.length - 1)) {
2173+
const optionsIndex = remainderUrl.indexOf('?');
2174+
if (optionsIndex !== -1) {
2175+
options = remainderUrl.substring(optionsIndex + 1);
2176+
dbName = remainderUrl.substring(slashIndex + 1, optionsIndex);
2177+
} else {
2178+
// No DB options specified
2179+
dbName = remainderUrl.substring(slashIndex + 1);
2180+
}
2181+
}
2182+
2183+
if (hostsArray.length > 1) {
2184+
const newHosts = hostsArray;
2185+
newHosts.pop();
2186+
newHosts.push(remainderUrl.substring(0, slashIndex));
2187+
hosts = newHosts.join(',');
2188+
} else {
2189+
hosts = remainderUrl.substring(0, slashIndex);
2190+
}
2191+
} else {
2192+
// No database specified
2193+
if (hostsArray.length > 1)
2194+
hosts = hostsArray.join(',');
2195+
else
2196+
hosts = remainderUrl;
2197+
}
2198+
2199+
// 5. Reconstruct url, but this time add database from settings if URL didn't have it
2200+
// The below code has an overlap with generateMongoDBURL()
2201+
let modifiedUrl = baseUrl;
2202+
2203+
if (uidPassword)
2204+
modifiedUrl += uidPassword + '@';
2205+
if (hosts)
2206+
modifiedUrl += hosts;
2207+
2208+
modifiedUrl += '/' + (dbName ? dbName : settingsDatabase);
2209+
2210+
if (options)
2211+
modifiedUrl += '?' + options;
2212+
2213+
return modifiedUrl;
2214+
}
2215+
return mongoUrl;
2216+
}
2217+
21212218
function isInTransation(options) {
21222219
const ops = {};
21232220
if (options && options.transaction && options.transaction.isInTransation)

0 commit comments

Comments
 (0)