Skip to content

Commit

Permalink
chore(NODE-2998): move connection-string parsing into separate package (
Browse files Browse the repository at this point in the history
  • Loading branch information
addaleax authored and ljhaywar committed Nov 9, 2021
1 parent c960399 commit 290a7d6
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 72 deletions.
37 changes: 33 additions & 4 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
},
"dependencies": {
"bson": "^4.4.0",
"denque": "^1.5.0"
"denque": "^1.5.0",
"mongodb-connection-string-url": "^1.0.0"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
Expand Down
68 changes: 4 additions & 64 deletions src/connection_string.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as dns from 'dns';
import * as fs from 'fs';
import { URL, URLSearchParams } from 'url';
import ConnectionString from 'mongodb-connection-string-url';
import { URLSearchParams } from 'url';
import { AuthMechanism } from './cmap/auth/defaultAuthProviders';
import { ReadPreference, ReadPreferenceMode } from './read_preference';
import { ReadConcern, ReadConcernLevel } from './read_concern';
Expand Down Expand Up @@ -146,68 +147,6 @@ export function checkTLSOptions(options: AnyOptions): void {
check('tlsDisableCertificateRevocationCheck', 'tlsDisableOCSPEndpointCheck');
}

const HOSTS_REGEX = new RegExp(
String.raw`(?<protocol>mongodb(?:\+srv|)):\/\/(?:(?<username>[^:]*)(?::(?<password>[^@]*))?@)?(?<hosts>(?!:)[^\/?@]+)(?<rest>.*)`
);

/** @internal */
export function parseURI(uri: string): { isSRV: boolean; url: URL; hosts: string[] } {
const match = uri.match(HOSTS_REGEX);
if (!match) {
throw new MongoParseError(`Invalid connection string ${uri}`);
}

const protocol = match.groups?.protocol;
const username = match.groups?.username;
const password = match.groups?.password;
const hosts = match.groups?.hosts;
const rest = match.groups?.rest;

if (!protocol || !hosts) {
throw new MongoParseError('Invalid connection string, protocol and host(s) required');
}

decodeURIComponent(username ?? '');
decodeURIComponent(password ?? '');

// characters not permitted in username nor password Set([':', '/', '?', '#', '[', ']', '@'])
const illegalCharacters = new RegExp(String.raw`[:/?#\[\]@]`, 'gi');
if (username?.match(illegalCharacters)) {
throw new MongoParseError(`Username contains unescaped characters ${username}`);
}
if (!username || !password) {
const uriWithoutProtocol = uri.replace(`${protocol}://`, '');
if (uriWithoutProtocol.startsWith('@') || uriWithoutProtocol.startsWith(':')) {
throw new MongoParseError('URI contained empty userinfo section');
}
}

if (password?.match(illegalCharacters)) {
throw new MongoParseError('Password contains unescaped characters');
}

let authString = '';
if (typeof username === 'string') authString += username;
if (typeof password === 'string') authString += `:${password}`;

const isSRV = protocol.includes('srv');
const hostList = hosts.split(',');
const url = new URL(`${protocol.toLowerCase()}://${authString}@dummyHostname${rest}`);

if (isSRV && hostList.length !== 1) {
throw new MongoParseError('mongodb+srv URI cannot have multiple service names');
}
if (isSRV && hostList[0].includes(':')) {
throw new MongoParseError('mongodb+srv URI cannot have port number');
}

return {
isSRV,
url,
hosts: hosts.split(',')
};
}

const TRUTHS = new Set(['true', 't', '1', 'y', 'yes']);
const FALSEHOODS = new Set(['false', 'f', '0', 'n', 'no', '-1']);
function getBoolean(name: string, value: unknown): boolean {
Expand Down Expand Up @@ -285,7 +224,8 @@ export function parseOptions(
mongoClient = undefined;
}

const { url, hosts, isSRV } = parseURI(uri);
const url = new ConnectionString(uri);
const { hosts, isSRV } = url;

const mongoOptions = Object.create(null);
mongoOptions.hosts = isSRV ? [] : hosts.map(HostAddress.fromString);
Expand Down
2 changes: 1 addition & 1 deletion test/functional/mongo_client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('MongoClient', function () {
},
test() {
expect(() => this.configuration.newClient('user:password@localhost:27017/test')).to.throw(
'Invalid connection string user:password@localhost:27017/test'
'Invalid connection string "user:password@localhost:27017/test"'
);
}
});
Expand Down
5 changes: 3 additions & 2 deletions test/tools/runner/config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';
const ConnectionString = require('mongodb-connection-string-url').default;
const url = require('url');
const qs = require('querystring');
const { expect } = require('chai');

const { MongoClient } = require('../../../src/mongo_client');
const { Topology } = require('../../../src/sdam/topology');
const { TopologyType } = require('../../../src/sdam/common');
const { parseURI } = require('../../../src/connection_string');
const { HostAddress } = require('../../../src/utils');

/**
Expand Down Expand Up @@ -35,7 +35,8 @@ function convertToConnStringMap(obj) {

class TestConfiguration {
constructor(uri, context) {
const { url, hosts } = parseURI(uri);
const url = new ConnectionString(uri);
const { hosts } = url;
const hostAddresses = hosts.map(HostAddress.fromString);
this.topologyType = context.topologyType;
this.version = context.version;
Expand Down

0 comments on commit 290a7d6

Please sign in to comment.