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

Commit

Permalink
fix(uri_parser): support URI Options spec tests
Browse files Browse the repository at this point in the history
Fixes NODE-1740
  • Loading branch information
kvwalker authored Mar 25, 2019
1 parent fa189a5 commit c067dbc
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 13 deletions.
98 changes: 90 additions & 8 deletions lib/uri_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,17 @@ const CASE_TRANSLATION = {
serverselectiontimeoutms: 'serverSelectionTimeoutMS',
serverselectiontryonce: 'serverSelectionTryOnce',
heartbeatfrequencyms: 'heartbeatFrequencyMS',
appname: 'appName',
retrywrites: 'retryWrites',
uuidrepresentation: 'uuidRepresentation',
zlibcompressionlevel: 'zlibCompressionLevel'
zlibcompressionlevel: 'zlibCompressionLevel',
tlsallowinvalidcertificates: 'tlsAllowInvalidCertificates',
tlsallowinvalidhostnames: 'tlsAllowInvalidHostnames',
tlsinsecure: 'tlsInsecure',
tlscafile: 'tlsCAFile',
tlscertificatekeyfile: 'tlsCertificateKeyFile',
tlscertificatekeyfilepassword: 'tlsCertificateKeyFilePassword',
wtimeout: 'wTimeoutMS',
j: 'journal'
};

/**
Expand All @@ -225,6 +232,7 @@ function applyConnectionStringOption(obj, key, value, options) {
} else if (key === 'appname') {
value = decodeURIComponent(value);
} else if (key === 'readconcernlevel') {
obj['readConcernLevel'] = value;
key = 'readconcern';
value = { level: value };
}
Expand Down Expand Up @@ -256,12 +264,6 @@ function applyConnectionStringOption(obj, key, value, options) {
throw new MongoParseError('zlibCompressionLevel must be an integer between -1 and 9');
}

// special cases
if (key === 'compressors' || key === 'zlibcompressionlevel') {
obj.compression = obj.compression || {};
obj = obj.compression;
}

if (key === 'authmechanismproperties') {
if (typeof value.SERVICE_NAME === 'string') obj.gssapiServiceName = value.SERVICE_NAME;
if (typeof value.SERVICE_REALM === 'string') obj.gssapiServiceRealm = value.SERVICE_REALM;
Expand All @@ -270,6 +272,10 @@ function applyConnectionStringOption(obj, key, value, options) {
}
}

if (key === 'readpreferencetags' && Array.isArray(value)) {
value = splitArrayOfMultipleReadPreferenceTags(value);
}

// set the actual value
if (options.caseTranslate && CASE_TRANSLATION[key]) {
obj[CASE_TRANSLATION[key]] = value;
Expand All @@ -287,6 +293,20 @@ const USERNAME_REQUIRED_MECHANISMS = new Set([
'SCRAM-SHA-256'
]);

function splitArrayOfMultipleReadPreferenceTags(value) {
const parsedTags = [];

for (let i = 0; i < value.length; i++) {
parsedTags[i] = {};
value[i].split(',').forEach(individualTag => {
const splitTag = individualTag.split(':');
parsedTags[i][splitTag[0]] = splitTag[1];
});
}

return parsedTags;
}

/**
* Modifies the parsed connection string object taking into account expectations we
* have for authentication-related options.
Expand Down Expand Up @@ -364,6 +384,8 @@ function parseQueryString(query, options) {
const result = {};
let parsedQueryString = qs.parse(query);

checkTLSOptions(parsedQueryString);

for (const key in parsedQueryString) {
const value = parsedQueryString[key];
if (value === '' || value == null) {
Expand All @@ -384,6 +406,66 @@ function parseQueryString(query, options) {
return Object.keys(result).length ? result : null;
}

/**
* Checks a query string for invalid tls options according to the URI options spec.
*
* @param {string} queryString The query string to check
* @throws {MongoParseError}
*/
function checkTLSOptions(queryString) {
const queryStringKeys = Object.keys(queryString);
if (
queryStringKeys.indexOf('tlsInsecure') !== -1 &&
(queryStringKeys.indexOf('tlsAllowInvalidCertificates') !== -1 ||
queryStringKeys.indexOf('tlsAllowInvalidHostnames') !== -1)
) {
throw new MongoParseError(
'The `tlsInsecure` option cannot be used with `tlsAllowInvalidCertificates` or `tlsAllowInvalidHostnames`.'
);
}

const tlsValue = assertTlsOptionsAreEqual('tls', queryString, queryStringKeys);
const sslValue = assertTlsOptionsAreEqual('ssl', queryString, queryStringKeys);

if (tlsValue != null && sslValue != null) {
if (tlsValue !== sslValue) {
throw new MongoParseError('All values of `tls` and `ssl` must be the same.');
}
}
}

/**
* Checks a query string to ensure all tls/ssl options are the same.
*
* @param {string} key The key (tls or ssl) to check
* @param {string} queryString The query string to check
* @throws {MongoParseError}
* @return The value of the tls/ssl option
*/
function assertTlsOptionsAreEqual(optionName, queryString, queryStringKeys) {
const queryStringHasTLSOption = queryStringKeys.indexOf(optionName) !== -1;

let optionValue;
if (Array.isArray(queryString[optionName])) {
optionValue = queryString[optionName][0];
} else {
optionValue = queryString[optionName];
}

if (queryStringHasTLSOption) {
if (Array.isArray(queryString[optionName])) {
const firstValue = queryString[optionName][0];
queryString[optionName].forEach(tlsValue => {
if (tlsValue !== firstValue) {
throw new MongoParseError('All values of ${optionName} must be the same.');
}
});
}
}

return optionValue;
}

const PROTOCOL_MONGODB = 'mongodb';
const PROTOCOL_MONGODB_SRV = 'mongodb+srv';
const SUPPORTED_PROTOCOLS = [PROTOCOL_MONGODB, PROTOCOL_MONGODB_SRV];
Expand Down
23 changes: 18 additions & 5 deletions test/tests/unit/connection_string_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,8 @@ describe('Connection String', function() {
'mongodb://localhost/?compressors=zlib&zlibCompressionLevel=4',
(err, result) => {
expect(err).to.not.exist;
expect(result.options).to.have.property('compression');
expect(result.options.compression).to.eql({
compressors: ['zlib'],
zlibCompressionLevel: 4
});
expect(result.options.compressors).to.eql(['zlib']);
expect(result.options.zlibCompressionLevel).to.equal(4);

done();
}
Expand Down Expand Up @@ -161,6 +158,22 @@ describe('Connection String', function() {
});
});

it('should parse multiple readPreferenceTags', function(done) {
parseConnectionString(
'mongodb://localhost/?readPreferenceTags=dc:ny,rack:1&readPreferenceTags=dc:ny',
(err, result) => {
expect(err).to.not.exist;
expect(result.options).to.have.property('readPreferenceTags');
expect(result.options.readPreferenceTags).to.deep.equal([
{ dc: 'ny', rack: '1' },
{ dc: 'ny' }
]);

done();
}
);
});

describe('validation', function() {
it('should validate compression options', function(done) {
parseConnectionString('mongodb://localhost/?zlibCompressionLevel=15', err => {
Expand Down

0 comments on commit c067dbc

Please sign in to comment.