Skip to content
This repository was archived by the owner on Feb 4, 2022. It is now read-only.

Commit 82896ea

Browse files
committed
fix(uri-parser): add exemption list for number coercion in options
NODE-1675
1 parent 39f2950 commit 82896ea

File tree

2 files changed

+38
-25
lines changed

2 files changed

+38
-25
lines changed

lib/uri_parser.js

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -132,27 +132,28 @@ function parseSrvConnectionString(uri, options, callback) {
132132
/**
133133
* Parses a query string item according to the connection string spec
134134
*
135+
* @param {string} key The key for the parsed value
135136
* @param {Array|String} value The value to parse
136137
* @return {Array|Object|String} The parsed value
137138
*/
138-
function parseQueryStringItemValue(value) {
139+
function parseQueryStringItemValue(key, value) {
139140
if (Array.isArray(value)) {
140141
// deduplicate and simplify arrays
141142
value = value.filter((v, idx) => value.indexOf(v) === idx);
142143
if (value.length === 1) value = value[0];
143144
} else if (value.indexOf(':') > 0) {
144145
value = value.split(',').reduce((result, pair) => {
145146
const parts = pair.split(':');
146-
result[parts[0]] = parseQueryStringItemValue(parts[1]);
147+
result[parts[0]] = parseQueryStringItemValue(key, parts[1]);
147148
return result;
148149
}, {});
149150
} else if (value.indexOf(',') > 0) {
150151
value = value.split(',').map(v => {
151-
return parseQueryStringItemValue(v);
152+
return parseQueryStringItemValue(key, v);
152153
});
153154
} else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
154155
value = value.toLowerCase() === 'true';
155-
} else if (!Number.isNaN(value)) {
156+
} else if (!Number.isNaN(value) && !STRING_OPTIONS.has(key)) {
156157
const numericValue = parseFloat(value);
157158
if (!Number.isNaN(numericValue)) {
158159
value = parseFloat(value);
@@ -173,6 +174,9 @@ const BOOLEAN_OPTIONS = new Set([
173174
'j'
174175
]);
175176

177+
// Known string options, only used to bypass Number coercion in `parseQueryStringItemValue`
178+
const STRING_OPTIONS = new Set(['authsource']);
179+
176180
// Supported text representations of auth mechanisms
177181
// NOTE: this list exists in native already, if it is merged here we should deduplicate
178182
const AUTH_MECHANISMS = new Set([
@@ -225,27 +229,25 @@ const CASE_TRANSLATION = {
225229
* @param {object} options The options used for option parsing
226230
*/
227231
function applyConnectionStringOption(obj, key, value, options) {
228-
let normalizedKey = key.toLowerCase();
229-
230232
// simple key translation
231-
if (normalizedKey === 'journal') {
232-
normalizedKey = 'j';
233-
} else if (normalizedKey === 'wtimeoutms') {
234-
normalizedKey = 'wtimeout';
233+
if (key === 'journal') {
234+
key = 'j';
235+
} else if (key === 'wtimeoutms') {
236+
key = 'wtimeout';
235237
}
236238

237239
// more complicated translation
238-
if (BOOLEAN_OPTIONS.has(normalizedKey)) {
240+
if (BOOLEAN_OPTIONS.has(key)) {
239241
value = value === 'true' || value === true;
240-
} else if (normalizedKey === 'appname') {
242+
} else if (key === 'appname') {
241243
value = decodeURIComponent(value);
242-
} else if (normalizedKey === 'readconcernlevel') {
243-
normalizedKey = 'readconcern';
244+
} else if (key === 'readconcernlevel') {
245+
key = 'readconcern';
244246
value = { level: value };
245247
}
246248

247249
// simple validation
248-
if (normalizedKey === 'compressors') {
250+
if (key === 'compressors') {
249251
value = Array.isArray(value) ? value : [value];
250252

251253
if (!value.every(c => c === 'snappy' || c === 'zlib')) {
@@ -255,29 +257,29 @@ function applyConnectionStringOption(obj, key, value, options) {
255257
}
256258
}
257259

258-
if (normalizedKey === 'authmechanism' && !AUTH_MECHANISMS.has(value)) {
260+
if (key === 'authmechanism' && !AUTH_MECHANISMS.has(value)) {
259261
return new MongoParseError(
260262
'Value for `authMechanism` must be one of: `DEFAULT`, `GSSAPI`, `PLAIN`, `MONGODB-X509`, `SCRAM-SHA-1`, `SCRAM-SHA-256`'
261263
);
262264
}
263265

264-
if (normalizedKey === 'readpreference' && !ReadPreference.isValid(value)) {
266+
if (key === 'readpreference' && !ReadPreference.isValid(value)) {
265267
return new MongoParseError(
266268
'Value for `readPreference` must be one of: `primary`, `primaryPreferred`, `secondary`, `secondaryPreferred`, `nearest`'
267269
);
268270
}
269271

270-
if (normalizedKey === 'zlibcompressionlevel' && (value < -1 || value > 9)) {
272+
if (key === 'zlibcompressionlevel' && (value < -1 || value > 9)) {
271273
return new MongoParseError('zlibCompressionLevel must be an integer between -1 and 9');
272274
}
273275

274276
// special cases
275-
if (normalizedKey === 'compressors' || normalizedKey === 'zlibcompressionlevel') {
277+
if (key === 'compressors' || key === 'zlibcompressionlevel') {
276278
obj.compression = obj.compression || {};
277279
obj = obj.compression;
278280
}
279281

280-
if (normalizedKey === 'authmechanismproperties') {
282+
if (key === 'authmechanismproperties') {
281283
if (typeof value.SERVICE_NAME === 'string') obj.gssapiServiceName = value.SERVICE_NAME;
282284
if (typeof value.SERVICE_REALM === 'string') obj.gssapiServiceRealm = value.SERVICE_REALM;
283285
if (typeof value.CANONICALIZE_HOST_NAME !== 'undefined') {
@@ -286,12 +288,12 @@ function applyConnectionStringOption(obj, key, value, options) {
286288
}
287289

288290
// set the actual value
289-
if (options.caseTranslate && CASE_TRANSLATION[normalizedKey]) {
290-
obj[CASE_TRANSLATION[normalizedKey]] = value;
291+
if (options.caseTranslate && CASE_TRANSLATION[key]) {
292+
obj[CASE_TRANSLATION[key]] = value;
291293
return;
292294
}
293295

294-
obj[normalizedKey] = value;
296+
obj[key] = value;
295297
}
296298

297299
/**
@@ -311,8 +313,9 @@ function parseQueryString(query, options) {
311313
return new MongoParseError('Incomplete key value pair for option');
312314
}
313315

314-
const parsedValue = parseQueryStringItemValue(value);
315-
const applyResult = applyConnectionStringOption(result, key, parsedValue, options);
316+
const normalizedKey = key.toLowerCase();
317+
const parsedValue = parseQueryStringItemValue(normalizedKey, value);
318+
const applyResult = applyConnectionStringOption(result, normalizedKey, parsedValue, options);
316319
if (applyResult instanceof MongoParseError) {
317320
return applyResult;
318321
}

test/tests/unit/connection_string_tests.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ describe('Connection String', function() {
107107
);
108108
});
109109

110+
it('should parse a numeric authSource with variable width', function(done) {
111+
parseConnectionString('mongodb://localhost/?authSource=0001', (err, result) => {
112+
expect(err).to.not.exist;
113+
expect(result.options).to.have.property('authSource');
114+
expect(result.options.authSource).to.equal('0001');
115+
116+
done();
117+
});
118+
});
119+
110120
describe('validation', function() {
111121
it('should validate compression options', function(done) {
112122
parseConnectionString('mongodb://localhost/?zlibCompressionLevel=15', err => {

0 commit comments

Comments
 (0)