Skip to content

Commit 11631a2

Browse files
nbbeekendurran
andauthored
feat(NODE-3920)!: validate options are not repeated in connection string (#3788)
Co-authored-by: Durran Jordan <[email protected]>
1 parent 68adaf1 commit 11631a2

File tree

4 files changed

+51
-36
lines changed

4 files changed

+51
-36
lines changed

src/connection_string.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ function getUIntFromOptions(name: string, value: unknown): number {
204204
}
205205

206206
function* entriesFromString(value: string): Generator<[string, string]> {
207+
if (value === '') {
208+
return;
209+
}
207210
const keyValuePairs = value.split(',');
208211
for (const keyValue of keyValuePairs) {
209212
const [key, value] = keyValue.split(/:(.*)/);
@@ -290,10 +293,18 @@ export function parseOptions(
290293
}
291294

292295
for (const key of url.searchParams.keys()) {
293-
const values = [...url.searchParams.getAll(key)];
296+
const values = url.searchParams.getAll(key);
297+
298+
const isReadPreferenceTags = /readPreferenceTags/i.test(key);
299+
300+
if (!isReadPreferenceTags && values.length > 1) {
301+
throw new MongoInvalidArgumentError(
302+
`URI option "${key}" cannot appear more than once in the connection string`
303+
);
304+
}
294305

295-
if (values.includes('')) {
296-
throw new MongoAPIError('URI cannot contain options with no value');
306+
if (!isReadPreferenceTags && values.includes('')) {
307+
throw new MongoAPIError(`URI option "${key}" cannot be specified with no value`);
297308
}
298309

299310
if (!urlOptions.has(key)) {

test/unit/connection_string.spec.test.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ const skipTests = [
1010
];
1111

1212
describe('Connection String spec tests', function () {
13-
// TODO(NODE-3920): validate repeated options
14-
const testsThatDoNotThrowOnWarn = ['Repeated option keys'];
1513
const suites = loadSpecTests('connection-string');
1614

1715
for (const suite of suites) {
@@ -22,10 +20,7 @@ describe('Connection String spec tests', function () {
2220
return this.skip();
2321
}
2422

25-
executeUriValidationTest(
26-
test,
27-
testsThatDoNotThrowOnWarn.some(t => t === test.description)
28-
);
23+
executeUriValidationTest(test);
2924
});
3025
}
3126
});

test/unit/connection_string.test.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ describe('Connection String', function () {
4444
});
4545
});
4646

47+
it('throws an error related to the option that was given an empty value', function () {
48+
expect(() => parseOptions('mongodb://localhost?tls=', {})).to.throw(
49+
MongoAPIError,
50+
/tls" cannot/i
51+
);
52+
});
53+
4754
it('should provide a default port if one is not provided', function () {
4855
const options = parseOptions('mongodb://hostname');
4956
expect(options.hosts[0].socketPath).to.be.undefined;
@@ -122,21 +129,18 @@ describe('Connection String', function () {
122129

123130
describe('readPreferenceTags option', function () {
124131
context('when the option is passed in the uri', () => {
125-
it('should throw an error if no value is passed for readPreferenceTags', () => {
126-
expect(() => parseOptions('mongodb://hostname?readPreferenceTags=')).to.throw(
127-
MongoAPIError
128-
);
129-
});
130132
it('should parse a single read preference tag', () => {
131133
const options = parseOptions('mongodb://hostname?readPreferenceTags=bar:foo');
132134
expect(options.readPreference.tags).to.deep.equal([{ bar: 'foo' }]);
133135
});
136+
134137
it('should parse multiple readPreferenceTags', () => {
135138
const options = parseOptions(
136139
'mongodb://hostname?readPreferenceTags=bar:foo&readPreferenceTags=baz:bar'
137140
);
138141
expect(options.readPreference.tags).to.deep.equal([{ bar: 'foo' }, { baz: 'bar' }]);
139142
});
143+
140144
it('should parse multiple readPreferenceTags for the same key', () => {
141145
const options = parseOptions(
142146
'mongodb://hostname?readPreferenceTags=bar:foo&readPreferenceTags=bar:banana&readPreferenceTags=baz:bar'
@@ -147,6 +151,19 @@ describe('Connection String', function () {
147151
{ baz: 'bar' }
148152
]);
149153
});
154+
155+
it('should parse multiple and empty readPreferenceTags', () => {
156+
const options = parseOptions(
157+
'mongodb://hostname?readPreferenceTags=bar:foo&readPreferenceTags=baz:bar&readPreferenceTags='
158+
);
159+
expect(options.readPreference.tags).to.deep.equal([{ bar: 'foo' }, { baz: 'bar' }, {}]);
160+
});
161+
162+
it('will set "__proto__" as own property on readPreferenceTag', () => {
163+
const options = parseOptions('mongodb://hostname?readPreferenceTags=__proto__:foo');
164+
expect(options.readPreference.tags?.[0]).to.have.own.property('__proto__', 'foo');
165+
expect(Object.getPrototypeOf(options.readPreference.tags?.[0])).to.be.null;
166+
});
150167
});
151168

152169
context('when the option is passed in the options object', () => {
@@ -156,12 +173,14 @@ describe('Connection String', function () {
156173
});
157174
expect(options.readPreference.tags).to.deep.equal([]);
158175
});
176+
159177
it('should parse a single readPreferenceTags object', () => {
160178
const options = parseOptions('mongodb://hostname?', {
161179
readPreferenceTags: [{ bar: 'foo' }]
162180
});
163181
expect(options.readPreference.tags).to.deep.equal([{ bar: 'foo' }]);
164182
});
183+
165184
it('should parse multiple readPreferenceTags', () => {
166185
const options = parseOptions('mongodb://hostname?', {
167186
readPreferenceTags: [{ bar: 'foo' }, { baz: 'bar' }]
@@ -493,6 +512,18 @@ describe('Connection String', function () {
493512
);
494513
});
495514

515+
it('throws an error for repeated options that can only appear once', function () {
516+
// At the time of writing, readPreferenceTags is the only options that can be repeated
517+
expect(() => parseOptions('mongodb://localhost/?compressors=zstd&compressors=zstd')).to.throw(
518+
MongoInvalidArgumentError,
519+
/cannot appear more than once/
520+
);
521+
expect(() => parseOptions('mongodb://localhost/?tls=true&tls=true')).to.throw(
522+
MongoInvalidArgumentError,
523+
/cannot appear more than once/
524+
);
525+
});
526+
496527
it('should validate authMechanism', function () {
497528
expect(() => parseOptions('mongodb://localhost/?authMechanism=DOGS')).to.throw(
498529
MongoParseError,

test/unit/mongo_client.test.js

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -400,18 +400,6 @@ describe('MongoOptions', function () {
400400
});
401401
});
402402

403-
it('throws an error if multiple tls parameters are not all set to the same value', () => {
404-
expect(() => parseOptions('mongodb://localhost?tls=true&tls=false')).to.throw(
405-
'All values of tls/ssl must be the same.'
406-
);
407-
});
408-
409-
it('throws an error if multiple ssl parameters are not all set to the same value', () => {
410-
expect(() => parseOptions('mongodb://localhost?ssl=true&ssl=false')).to.throw(
411-
'All values of tls/ssl must be the same.'
412-
);
413-
});
414-
415403
it('throws an error if tls and ssl parameters are not all set to the same value', () => {
416404
expect(() => parseOptions('mongodb://localhost?tls=true&ssl=false')).to.throw(
417405
'All values of tls/ssl must be the same.'
@@ -421,16 +409,6 @@ describe('MongoOptions', function () {
421409
);
422410
});
423411

424-
it('correctly sets tls if multiple tls parameters are all set to the same value', () => {
425-
expect(parseOptions('mongodb://localhost?tls=true&tls=true')).to.have.property('tls', true);
426-
expect(parseOptions('mongodb://localhost?tls=false&tls=false')).to.have.property('tls', false);
427-
});
428-
429-
it('correctly sets tls if multiple ssl parameters are all set to the same value', () => {
430-
expect(parseOptions('mongodb://localhost?ssl=true&ssl=true')).to.have.property('tls', true);
431-
expect(parseOptions('mongodb://localhost?ssl=false&ssl=false')).to.have.property('tls', false);
432-
});
433-
434412
it('correctly sets tls if tls and ssl parameters are all set to the same value', () => {
435413
expect(parseOptions('mongodb://localhost?ssl=true&tls=true')).to.have.property('tls', true);
436414
expect(parseOptions('mongodb://localhost?ssl=false&tls=false')).to.have.property('tls', false);

0 commit comments

Comments
 (0)