@@ -17,9 +17,8 @@ function connectionStringHasValidScheme(connectionString: string) {
1717
1818// Adapted from the Node.js driver code:
1919// https://github.com/mongodb/node-mongodb-native/blob/350d14fde5b24480403313cfe5044f6e4b25f6c9/src/connection_string.ts#L146-L206
20- const HOSTS_REGEX = new RegExp (
21- String . raw `^(?<protocol>mongodb(?:\+srv|)):\/\/(?:(?<username>[^:]*)(?::(?<password>[^@]*))?@)?(?<hosts>(?!:)[^\/?@]*)(?<rest>.*)`
22- ) ;
20+ const HOSTS_REGEX =
21+ / ^ (?< protocol > [ ^ / ] + ) : \/ \/ (?: (?< username > [ ^ : ] * ) (?: : (?< password > [ ^ @ ] * ) ) ? @ ) ? (?< hosts > (? ! : ) [ ^ / ? @ ] * ) (?< rest > .* ) / ;
2322
2423class CaseInsensitiveMap < K extends string = string > extends Map < K , string > {
2524 delete ( name : K ) : boolean {
@@ -116,16 +115,21 @@ class MongoParseError extends Error {
116115 }
117116}
118117
118+ export interface ConnectionStringParsingOptions {
119+ looseValidation ?: boolean ;
120+ }
121+
119122/**
120123 * Represents a mongodb:// or mongodb+srv:// connection string.
121124 * See: https://github.com/mongodb/specifications/blob/master/source/connection-string/connection-string-spec.rst#reference-implementation
122125 */
123- export default class ConnectionString extends URLWithoutHost {
126+ export class ConnectionString extends URLWithoutHost {
124127 _hosts : string [ ] ;
125128
126129 // eslint-disable-next-line complexity
127- constructor ( uri : string ) {
128- if ( ! connectionStringHasValidScheme ( uri ) ) {
130+ constructor ( uri : string , options : ConnectionStringParsingOptions = { } ) {
131+ const { looseValidation } = options ;
132+ if ( ! looseValidation && ! connectionStringHasValidScheme ( uri ) ) {
129133 throw new MongoParseError ( 'Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"' ) ;
130134 }
131135
@@ -136,31 +140,33 @@ export default class ConnectionString extends URLWithoutHost {
136140
137141 const { protocol, username, password, hosts, rest } = match . groups ?? { } ;
138142
139- if ( ! protocol || ! hosts ) {
140- throw new MongoParseError ( `Protocol and host list are required in "${ uri } "` ) ;
141- }
143+ if ( ! looseValidation ) {
144+ if ( ! protocol || ! hosts ) {
145+ throw new MongoParseError ( `Protocol and host list are required in "${ uri } "` ) ;
146+ }
142147
143- try {
144- decodeURIComponent ( username ?? '' ) ;
145- decodeURIComponent ( password ?? '' ) ;
146- } catch ( err ) {
147- throw new MongoParseError ( ( err as Error ) . message ) ;
148- }
148+ try {
149+ decodeURIComponent ( username ?? '' ) ;
150+ decodeURIComponent ( password ?? '' ) ;
151+ } catch ( err ) {
152+ throw new MongoParseError ( ( err as Error ) . message ) ;
153+ }
149154
150- // characters not permitted in username nor password Set([':', '/', '?', '#', '[', ']', '@'])
151- const illegalCharacters = new RegExp ( String . raw `[:/?#\[\]@]` , 'gi' ) ;
152- if ( username ?. match ( illegalCharacters ) ) {
153- throw new MongoParseError ( `Username contains unescaped characters ${ username } ` ) ;
154- }
155- if ( ! username || ! password ) {
156- const uriWithoutProtocol = uri . replace ( `${ protocol } ://` , '' ) ;
157- if ( uriWithoutProtocol . startsWith ( '@' ) || uriWithoutProtocol . startsWith ( ':' ) ) {
158- throw new MongoParseError ( 'URI contained empty userinfo section' ) ;
155+ // characters not permitted in username nor password Set([':', '/', '?', '#', '[', ']', '@'])
156+ const illegalCharacters = / [: / ? # [ \] @ ] / gi;
157+ if ( username ?. match ( illegalCharacters ) ) {
158+ throw new MongoParseError ( `Username contains unescaped characters ${ username } ` ) ;
159+ }
160+ if ( ! username || ! password ) {
161+ const uriWithoutProtocol = uri . replace ( `${ protocol } ://` , '' ) ;
162+ if ( uriWithoutProtocol . startsWith ( '@' ) || uriWithoutProtocol . startsWith ( ':' ) ) {
163+ throw new MongoParseError ( 'URI contained empty userinfo section' ) ;
164+ }
159165 }
160- }
161166
162- if ( password ?. match ( illegalCharacters ) ) {
163- throw new MongoParseError ( 'Password contains unescaped characters' ) ;
167+ if ( password ?. match ( illegalCharacters ) ) {
168+ throw new MongoParseError ( 'Password contains unescaped characters' ) ;
169+ }
164170 }
165171
166172 let authString = '' ;
@@ -171,12 +177,15 @@ export default class ConnectionString extends URLWithoutHost {
171177 super ( `${ protocol . toLowerCase ( ) } ://${ authString } ${ DUMMY_HOSTNAME } ${ rest } ` ) ;
172178 this . _hosts = hosts . split ( ',' ) ;
173179
174- if ( this . isSRV && this . hosts . length !== 1 ) {
175- throw new MongoParseError ( 'mongodb+srv URI cannot have multiple service names' ) ;
176- }
177- if ( this . isSRV && this . hosts . some ( host => host . includes ( ':' ) ) ) {
178- throw new MongoParseError ( 'mongodb+srv URI cannot have port number' ) ;
180+ if ( ! looseValidation ) {
181+ if ( this . isSRV && this . hosts . length !== 1 ) {
182+ throw new MongoParseError ( 'mongodb+srv URI cannot have multiple service names' ) ;
183+ }
184+ if ( this . isSRV && this . hosts . some ( host => host . includes ( ':' ) ) ) {
185+ throw new MongoParseError ( 'mongodb+srv URI cannot have port number' ) ;
186+ }
179187 }
188+
180189 if ( ! this . pathname ) {
181190 this . pathname = '/' ;
182191 }
@@ -255,3 +264,5 @@ export class CommaAndColonSeparatedRecord<K extends {} = Record<string, unknown>
255264 return [ ...this ] . map ( entry => entry . join ( ':' ) ) . join ( ',' ) ;
256265 }
257266}
267+
268+ export default ConnectionString ;
0 commit comments