@qp-mongosh/service-provider-core 0.0.0-dev.5 → 0.0.0-dev.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -2
- package/.eslintrc.js +1 -1
- package/AUTHORS +11 -11
- package/LICENSE +200 -200
- package/lib/admin.d.ts +28 -28
- package/lib/admin.js +2 -2
- package/lib/all-fle-types.d.ts +2 -2
- package/lib/all-fle-types.js +2 -2
- package/lib/all-transport-types.d.ts +1 -1
- package/lib/all-transport-types.js +2 -2
- package/lib/bulk-write-result.d.ts +16 -0
- package/lib/bulk-write-result.js +3 -0
- package/lib/bulk-write-result.js.map +1 -0
- package/lib/cli-options.d.ts +43 -43
- package/lib/cli-options.js +2 -2
- package/lib/closable.d.ts +4 -4
- package/lib/closable.js +2 -2
- package/lib/command-options.d.ts +4 -0
- package/lib/command-options.js +3 -0
- package/lib/command-options.js.map +1 -0
- package/lib/connect-info.d.ts +19 -19
- package/lib/connect-info.js +34 -34
- package/lib/cursor.d.ts +34 -0
- package/lib/cursor.js +3 -0
- package/lib/cursor.js.map +1 -0
- package/lib/database-options.d.ts +8 -0
- package/lib/database-options.js +3 -0
- package/lib/database-options.js.map +1 -0
- package/lib/document.d.ts +3 -0
- package/lib/document.js +3 -0
- package/lib/document.js.map +1 -0
- package/lib/fast-failure-connect.d.ts +1 -0
- package/lib/fast-failure-connect.js +15 -0
- package/lib/fast-failure-connect.js.map +1 -0
- package/lib/index.d.ts +31 -31
- package/lib/index.js +55 -55
- package/lib/platform.d.ts +6 -6
- package/lib/platform.js +10 -10
- package/lib/printable-bson.d.ts +3 -3
- package/lib/printable-bson.js +81 -81
- package/lib/read-concern.d.ts +3 -0
- package/lib/read-concern.js +3 -0
- package/lib/read-concern.js.map +1 -0
- package/lib/read-preference.d.ts +4 -0
- package/lib/read-preference.js +3 -0
- package/lib/read-preference.js.map +1 -0
- package/lib/readable.d.ts +18 -18
- package/lib/readable.js +2 -2
- package/lib/result.d.ts +2 -0
- package/lib/result.js +3 -0
- package/lib/result.js.map +1 -0
- package/lib/service-provider.d.ts +11 -11
- package/lib/service-provider.js +18 -18
- package/lib/shell-auth-options.d.ts +7 -7
- package/lib/shell-auth-options.js +2 -2
- package/lib/textencoder-polyfill.d.ts +2 -2
- package/lib/textencoder-polyfill.js +21 -21
- package/lib/uri-generator.d.ts +8 -8
- package/lib/uri-generator.js +175 -175
- package/lib/writable.d.ts +22 -22
- package/lib/writable.js +2 -2
- package/lib/write-concern.d.ts +5 -0
- package/lib/write-concern.js +3 -0
- package/lib/write-concern.js.map +1 -0
- package/package.json +54 -54
- package/src/admin.ts +119 -119
- package/src/all-fle-types.ts +17 -17
- package/src/all-transport-types.ts +80 -80
- package/src/cli-options.ts +49 -49
- package/src/closable.ts +14 -14
- package/src/connect-info.spec.ts +192 -192
- package/src/connect-info.ts +57 -57
- package/src/index.ts +62 -62
- package/src/platform.ts +6 -6
- package/src/printable-bson.spec.ts +75 -75
- package/src/printable-bson.ts +103 -103
- package/src/readable.ts +242 -242
- package/src/service-provider.ts +23 -23
- package/src/shell-auth-options.ts +7 -7
- package/src/textencoder-polyfill.spec.ts +11 -11
- package/src/textencoder-polyfill.ts +30 -30
- package/src/uri-generator.spec.ts +481 -481
- package/src/uri-generator.ts +265 -265
- package/src/writable.ts +367 -367
- package/tsconfig.json +12 -12
- package/tsconfig.lint.json +8 -8
package/src/uri-generator.ts
CHANGED
|
@@ -1,265 +1,265 @@
|
|
|
1
|
-
/* eslint complexity: 0*/
|
|
2
|
-
|
|
3
|
-
import { CommonErrors, MongoshInvalidInputError } from '@qp-mongosh/errors';
|
|
4
|
-
import i18n from '@qp-mongosh/i18n';
|
|
5
|
-
import CliOptions from './cli-options';
|
|
6
|
-
import ConnectionString, { CommaAndColonSeparatedRecord } from 'mongodb-connection-string-url';
|
|
7
|
-
import { DEFAULT_DB } from './index';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* URI schemes.
|
|
11
|
-
*/
|
|
12
|
-
enum Scheme {
|
|
13
|
-
Mongo = 'mongodb://',
|
|
14
|
-
MongoSrv = 'mongodb+srv://'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The default host.
|
|
19
|
-
*/
|
|
20
|
-
const DEFAULT_HOST = '127.0.0.1';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The default port.
|
|
24
|
-
*/
|
|
25
|
-
const DEFAULT_PORT = '27017';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Conflicting host/port message.
|
|
29
|
-
*/
|
|
30
|
-
const CONFLICT = 'cli-repl.uri-generator.no-host-port';
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Invalid host message.
|
|
34
|
-
*/
|
|
35
|
-
const INVALID_HOST = 'cli-repl.uri-generator.invalid-host';
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Host seed list contains a port that mismatches an explicit --port.
|
|
39
|
-
*/
|
|
40
|
-
const HOST_LIST_PORT_MISMATCH = 'cli-repl.uri-generator.host-list-port-mismatch';
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Diverging gssapiServiceName and SERVICE_NAME mechanism property
|
|
44
|
-
*/
|
|
45
|
-
const DIVERGING_SERVICE_NAME = 'cli-repl.uri-generator.diverging-service-name';
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Usage of unsupported gssapiServiceName query parameter
|
|
49
|
-
*/
|
|
50
|
-
const GSSAPI_SERVICE_NAME_UNSUPPORTED = 'cli-repl.uri-generator.gssapi-service-name-unsupported';
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Validate conflicts in the options.
|
|
54
|
-
*/
|
|
55
|
-
function validateConflicts(options: CliOptions, connectionString?: ConnectionString): void {
|
|
56
|
-
if (options.host || options.port) {
|
|
57
|
-
throw new MongoshInvalidInputError(i18n.__(CONFLICT), CommonErrors.InvalidArgument);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (options.gssapiServiceName && connectionString?.searchParams.has('authMechanismProperties')) {
|
|
61
|
-
const authProperties = new CommaAndColonSeparatedRecord(
|
|
62
|
-
connectionString.searchParams.get('authMechanismProperties'));
|
|
63
|
-
const serviceName = authProperties.get('SERVICE_NAME');
|
|
64
|
-
if (serviceName !== undefined && options.gssapiServiceName !== serviceName) {
|
|
65
|
-
throw new MongoshInvalidInputError(i18n.__(DIVERGING_SERVICE_NAME), CommonErrors.InvalidArgument);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (connectionString?.searchParams.has('gssapiServiceName')) {
|
|
70
|
-
throw new MongoshInvalidInputError(i18n.__(GSSAPI_SERVICE_NAME_UNSUPPORTED), CommonErrors.InvalidArgument);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Perform basic validation of the --host option.
|
|
76
|
-
*
|
|
77
|
-
* @param {string} host - The value of the --host option.
|
|
78
|
-
*/
|
|
79
|
-
function validateHost(host: string): void {
|
|
80
|
-
const invalidCharacter = host.match(/[^a-zA-Z0-9.:\[\]_-]/);
|
|
81
|
-
if (invalidCharacter) {
|
|
82
|
-
throw new MongoshInvalidInputError(
|
|
83
|
-
i18n.__(INVALID_HOST) + ': ' + invalidCharacter[0],
|
|
84
|
-
CommonErrors.InvalidArgument);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Validates a host seed list against a specified fixed port and
|
|
90
|
-
* returns an individual `<host>:<port>` array.
|
|
91
|
-
*/
|
|
92
|
-
function validateHostSeedList(hosts: string, fixedPort: string | undefined): string[] {
|
|
93
|
-
const trimmedHosts = hosts.split(',').map(h => h.trim()).filter(h => !!h);
|
|
94
|
-
const hostList: string[] = [];
|
|
95
|
-
trimmedHosts.forEach(h => {
|
|
96
|
-
const [host, port] = h.split(':');
|
|
97
|
-
if (fixedPort && port !== undefined && port !== fixedPort) {
|
|
98
|
-
throw new MongoshInvalidInputError(
|
|
99
|
-
i18n.__(HOST_LIST_PORT_MISMATCH),
|
|
100
|
-
CommonErrors.InvalidArgument
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
hostList.push(`${host}${(port || fixedPort) ? ':' + (port || fixedPort) : ''}`);
|
|
104
|
-
});
|
|
105
|
-
return hostList;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Generate the host from the options or default.
|
|
110
|
-
*
|
|
111
|
-
* @param {CliOptions} options - The options.
|
|
112
|
-
*
|
|
113
|
-
* @returns {string} The host.
|
|
114
|
-
*/
|
|
115
|
-
function generateHost(options: CliOptions): string {
|
|
116
|
-
if (options.host) {
|
|
117
|
-
validateHost(options.host);
|
|
118
|
-
if (options.host.includes(':')) {
|
|
119
|
-
return options.host.split(':')[0];
|
|
120
|
-
}
|
|
121
|
-
return options.host;
|
|
122
|
-
}
|
|
123
|
-
return DEFAULT_HOST;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Generate the port from the options or default.
|
|
128
|
-
*
|
|
129
|
-
* @param {CliOptions} options - The options.
|
|
130
|
-
*
|
|
131
|
-
* @returns {string} The port.
|
|
132
|
-
*/
|
|
133
|
-
function generatePort(options: CliOptions): string {
|
|
134
|
-
if (options.host && options.host.includes(':')) {
|
|
135
|
-
validateHost(options.host);
|
|
136
|
-
const port = options.host.split(':')[1];
|
|
137
|
-
if (!options.port || options.port === port) {
|
|
138
|
-
return port;
|
|
139
|
-
}
|
|
140
|
-
throw new MongoshInvalidInputError(i18n.__(CONFLICT), CommonErrors.InvalidArgument);
|
|
141
|
-
}
|
|
142
|
-
return options.port ? options.port : DEFAULT_PORT;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Generate a URI from the provided CLI options.
|
|
147
|
-
*
|
|
148
|
-
* If a full URI is provided, you cannot also specify --host or --port
|
|
149
|
-
*
|
|
150
|
-
* Rules from the existing Shell code:
|
|
151
|
-
*
|
|
152
|
-
* if nodb is set then all positional parameters are files
|
|
153
|
-
* otherwise the first positional parameter might be a dbaddress, but
|
|
154
|
-
* only if one of these conditions is met:
|
|
155
|
-
* - it contains no '.' after the last appearance of '\' or '/'
|
|
156
|
-
* - it doesn't end in '.js' and it doesn't specify a path to an existing file
|
|
157
|
-
*/
|
|
158
|
-
function generateUri(options: CliOptions): string {
|
|
159
|
-
if (options.nodb) {
|
|
160
|
-
return '';
|
|
161
|
-
}
|
|
162
|
-
const connectionString = generateUriNormalized(options);
|
|
163
|
-
if (connectionString.hosts.every(host =>
|
|
164
|
-
['localhost', '127.0.0.1'].includes(host.split(':')[0]))) {
|
|
165
|
-
const params = connectionString.searchParams;
|
|
166
|
-
if (!params.has('serverSelectionTimeoutMS')) {
|
|
167
|
-
params.set('serverSelectionTimeoutMS', '2000');
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return connectionString.toString();
|
|
171
|
-
}
|
|
172
|
-
function generateUriNormalized(options: CliOptions): ConnectionString {
|
|
173
|
-
const uri = options.connectionSpecifier;
|
|
174
|
-
|
|
175
|
-
// If the --host argument contains /, it has the format
|
|
176
|
-
// <replSetName>/<hostname1><:port>,<hostname2><:port>,<...>
|
|
177
|
-
const replSetHostMatch = (options.host ?? '').match(
|
|
178
|
-
/^(?<replSetName>[^/]+)\/(?<hosts>([A-Za-z0-9._-]+(:\d+)?,?)+)$/
|
|
179
|
-
);
|
|
180
|
-
if (replSetHostMatch) {
|
|
181
|
-
const { replSetName, hosts } = replSetHostMatch.groups as { replSetName: string, hosts: string };
|
|
182
|
-
const connectionString = new ConnectionString(`${Scheme.Mongo}replacemeHost/${encodeURIComponent(uri || '')}`);
|
|
183
|
-
connectionString.hosts = validateHostSeedList(hosts, options.port);
|
|
184
|
-
connectionString.searchParams.set('replicaSet', replSetName);
|
|
185
|
-
return addShellConnectionStringParameters(connectionString);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// If the --host argument contains multiple hosts as a seed list
|
|
189
|
-
// we directly do not do additional host/port parsing
|
|
190
|
-
const seedList = (options.host ?? '').match(
|
|
191
|
-
/^(?<hosts>([A-Za-z0-9._-]+(:\d+)?,?)+)$/
|
|
192
|
-
);
|
|
193
|
-
if (seedList && options.host?.includes(',')) {
|
|
194
|
-
const { hosts } = seedList.groups as { hosts: string };
|
|
195
|
-
const connectionString = new ConnectionString(`${Scheme.Mongo}replacemeHost/${encodeURIComponent(uri || '')}`);
|
|
196
|
-
connectionString.hosts = validateHostSeedList(hosts, options.port);
|
|
197
|
-
return addShellConnectionStringParameters(connectionString);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// There is no URI provided, use default 127.0.0.1:27017
|
|
201
|
-
if (!uri) {
|
|
202
|
-
return new ConnectionString(`${Scheme.Mongo}${generateHost(options)}:${generatePort(options)}/?directConnection=true`);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// mongodb+srv:// URI is provided, treat as correct and immediately return
|
|
206
|
-
if (uri.startsWith(Scheme.MongoSrv)) {
|
|
207
|
-
const connectionString = new ConnectionString(uri);
|
|
208
|
-
validateConflicts(options, connectionString);
|
|
209
|
-
return connectionString;
|
|
210
|
-
} else if (uri.startsWith(Scheme.Mongo)) {
|
|
211
|
-
// we need to figure out if we have to add the directConnection query parameter
|
|
212
|
-
const connectionString = new ConnectionString(uri);
|
|
213
|
-
validateConflicts(options, connectionString);
|
|
214
|
-
return addShellConnectionStringParameters(connectionString);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Capture host, port and db from the string and generate a URI from
|
|
218
|
-
// the parts. If there is a db part, it *must* start with /.
|
|
219
|
-
const uriMatch = /^([A-Za-z0-9][A-Za-z0-9._-]+):?(\d+)?(?:\/(\S*))?$/gi;
|
|
220
|
-
let parts: string[] | null = uriMatch.exec(uri);
|
|
221
|
-
|
|
222
|
-
if (parts === null) {
|
|
223
|
-
if (/[/\\. "$]/.test(uri)) {
|
|
224
|
-
// This cannot be a database name because 'uri' contains characters invalid in a database.
|
|
225
|
-
throw new MongoshInvalidInputError(`Invalid URI: ${uri}`, CommonErrors.InvalidArgument);
|
|
226
|
-
} else {
|
|
227
|
-
parts = [ uri, uri ];
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
let host: string | undefined = parts?.[1];
|
|
232
|
-
const port = parts?.[2];
|
|
233
|
-
let dbAndQueryString = parts?.[3];
|
|
234
|
-
|
|
235
|
-
// If there is no port and db, host becomes db if there is no
|
|
236
|
-
// '.' in the string. (legacy shell behaviour)
|
|
237
|
-
if (!port && !dbAndQueryString && host.indexOf('.') < 0) {
|
|
238
|
-
dbAndQueryString = host;
|
|
239
|
-
host = undefined;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// If we have a host or port, validate that the options don't also
|
|
243
|
-
// have a host or port in them.
|
|
244
|
-
if (host || port) {
|
|
245
|
-
validateConflicts(options);
|
|
246
|
-
}
|
|
247
|
-
return addShellConnectionStringParameters(new ConnectionString(
|
|
248
|
-
`${Scheme.Mongo}${host || generateHost(options)}:${port || generatePort(options)}/${encodeURIComponent(dbAndQueryString || DEFAULT_DB)}`));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Adds the `directConnection=true` query parameter if required.
|
|
253
|
-
* @param uri mongodb:// connection string
|
|
254
|
-
*/
|
|
255
|
-
function addShellConnectionStringParameters(uri: ConnectionString): ConnectionString {
|
|
256
|
-
uri = uri.clone();
|
|
257
|
-
const params = uri.searchParams;
|
|
258
|
-
if (!params.has('replicaSet') && !params.has('directConnection') && !params.has('loadBalanced') && uri.hosts.length === 1) {
|
|
259
|
-
params.set('directConnection', 'true');
|
|
260
|
-
}
|
|
261
|
-
return uri;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export default generateUri;
|
|
265
|
-
export { Scheme };
|
|
1
|
+
/* eslint complexity: 0*/
|
|
2
|
+
|
|
3
|
+
import { CommonErrors, MongoshInvalidInputError } from '@qp-mongosh/errors';
|
|
4
|
+
import i18n from '@qp-mongosh/i18n';
|
|
5
|
+
import CliOptions from './cli-options';
|
|
6
|
+
import ConnectionString, { CommaAndColonSeparatedRecord } from 'mongodb-connection-string-url';
|
|
7
|
+
import { DEFAULT_DB } from './index';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* URI schemes.
|
|
11
|
+
*/
|
|
12
|
+
enum Scheme {
|
|
13
|
+
Mongo = 'mongodb://',
|
|
14
|
+
MongoSrv = 'mongodb+srv://'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The default host.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The default port.
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_PORT = '27017';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Conflicting host/port message.
|
|
29
|
+
*/
|
|
30
|
+
const CONFLICT = 'cli-repl.uri-generator.no-host-port';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Invalid host message.
|
|
34
|
+
*/
|
|
35
|
+
const INVALID_HOST = 'cli-repl.uri-generator.invalid-host';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Host seed list contains a port that mismatches an explicit --port.
|
|
39
|
+
*/
|
|
40
|
+
const HOST_LIST_PORT_MISMATCH = 'cli-repl.uri-generator.host-list-port-mismatch';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Diverging gssapiServiceName and SERVICE_NAME mechanism property
|
|
44
|
+
*/
|
|
45
|
+
const DIVERGING_SERVICE_NAME = 'cli-repl.uri-generator.diverging-service-name';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Usage of unsupported gssapiServiceName query parameter
|
|
49
|
+
*/
|
|
50
|
+
const GSSAPI_SERVICE_NAME_UNSUPPORTED = 'cli-repl.uri-generator.gssapi-service-name-unsupported';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate conflicts in the options.
|
|
54
|
+
*/
|
|
55
|
+
function validateConflicts(options: CliOptions, connectionString?: ConnectionString): void {
|
|
56
|
+
if (options.host || options.port) {
|
|
57
|
+
throw new MongoshInvalidInputError(i18n.__(CONFLICT), CommonErrors.InvalidArgument);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (options.gssapiServiceName && connectionString?.searchParams.has('authMechanismProperties')) {
|
|
61
|
+
const authProperties = new CommaAndColonSeparatedRecord(
|
|
62
|
+
connectionString.searchParams.get('authMechanismProperties'));
|
|
63
|
+
const serviceName = authProperties.get('SERVICE_NAME');
|
|
64
|
+
if (serviceName !== undefined && options.gssapiServiceName !== serviceName) {
|
|
65
|
+
throw new MongoshInvalidInputError(i18n.__(DIVERGING_SERVICE_NAME), CommonErrors.InvalidArgument);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (connectionString?.searchParams.has('gssapiServiceName')) {
|
|
70
|
+
throw new MongoshInvalidInputError(i18n.__(GSSAPI_SERVICE_NAME_UNSUPPORTED), CommonErrors.InvalidArgument);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Perform basic validation of the --host option.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} host - The value of the --host option.
|
|
78
|
+
*/
|
|
79
|
+
function validateHost(host: string): void {
|
|
80
|
+
const invalidCharacter = host.match(/[^a-zA-Z0-9.:\[\]_-]/);
|
|
81
|
+
if (invalidCharacter) {
|
|
82
|
+
throw new MongoshInvalidInputError(
|
|
83
|
+
i18n.__(INVALID_HOST) + ': ' + invalidCharacter[0],
|
|
84
|
+
CommonErrors.InvalidArgument);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validates a host seed list against a specified fixed port and
|
|
90
|
+
* returns an individual `<host>:<port>` array.
|
|
91
|
+
*/
|
|
92
|
+
function validateHostSeedList(hosts: string, fixedPort: string | undefined): string[] {
|
|
93
|
+
const trimmedHosts = hosts.split(',').map(h => h.trim()).filter(h => !!h);
|
|
94
|
+
const hostList: string[] = [];
|
|
95
|
+
trimmedHosts.forEach(h => {
|
|
96
|
+
const [host, port] = h.split(':');
|
|
97
|
+
if (fixedPort && port !== undefined && port !== fixedPort) {
|
|
98
|
+
throw new MongoshInvalidInputError(
|
|
99
|
+
i18n.__(HOST_LIST_PORT_MISMATCH),
|
|
100
|
+
CommonErrors.InvalidArgument
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
hostList.push(`${host}${(port || fixedPort) ? ':' + (port || fixedPort) : ''}`);
|
|
104
|
+
});
|
|
105
|
+
return hostList;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generate the host from the options or default.
|
|
110
|
+
*
|
|
111
|
+
* @param {CliOptions} options - The options.
|
|
112
|
+
*
|
|
113
|
+
* @returns {string} The host.
|
|
114
|
+
*/
|
|
115
|
+
function generateHost(options: CliOptions): string {
|
|
116
|
+
if (options.host) {
|
|
117
|
+
validateHost(options.host);
|
|
118
|
+
if (options.host.includes(':')) {
|
|
119
|
+
return options.host.split(':')[0];
|
|
120
|
+
}
|
|
121
|
+
return options.host;
|
|
122
|
+
}
|
|
123
|
+
return DEFAULT_HOST;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Generate the port from the options or default.
|
|
128
|
+
*
|
|
129
|
+
* @param {CliOptions} options - The options.
|
|
130
|
+
*
|
|
131
|
+
* @returns {string} The port.
|
|
132
|
+
*/
|
|
133
|
+
function generatePort(options: CliOptions): string {
|
|
134
|
+
if (options.host && options.host.includes(':')) {
|
|
135
|
+
validateHost(options.host);
|
|
136
|
+
const port = options.host.split(':')[1];
|
|
137
|
+
if (!options.port || options.port === port) {
|
|
138
|
+
return port;
|
|
139
|
+
}
|
|
140
|
+
throw new MongoshInvalidInputError(i18n.__(CONFLICT), CommonErrors.InvalidArgument);
|
|
141
|
+
}
|
|
142
|
+
return options.port ? options.port : DEFAULT_PORT;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generate a URI from the provided CLI options.
|
|
147
|
+
*
|
|
148
|
+
* If a full URI is provided, you cannot also specify --host or --port
|
|
149
|
+
*
|
|
150
|
+
* Rules from the existing Shell code:
|
|
151
|
+
*
|
|
152
|
+
* if nodb is set then all positional parameters are files
|
|
153
|
+
* otherwise the first positional parameter might be a dbaddress, but
|
|
154
|
+
* only if one of these conditions is met:
|
|
155
|
+
* - it contains no '.' after the last appearance of '\' or '/'
|
|
156
|
+
* - it doesn't end in '.js' and it doesn't specify a path to an existing file
|
|
157
|
+
*/
|
|
158
|
+
function generateUri(options: CliOptions): string {
|
|
159
|
+
if (options.nodb) {
|
|
160
|
+
return '';
|
|
161
|
+
}
|
|
162
|
+
const connectionString = generateUriNormalized(options);
|
|
163
|
+
if (connectionString.hosts.every(host =>
|
|
164
|
+
['localhost', '127.0.0.1'].includes(host.split(':')[0]))) {
|
|
165
|
+
const params = connectionString.searchParams;
|
|
166
|
+
if (!params.has('serverSelectionTimeoutMS')) {
|
|
167
|
+
params.set('serverSelectionTimeoutMS', '2000');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return connectionString.toString();
|
|
171
|
+
}
|
|
172
|
+
function generateUriNormalized(options: CliOptions): ConnectionString {
|
|
173
|
+
const uri = options.connectionSpecifier;
|
|
174
|
+
|
|
175
|
+
// If the --host argument contains /, it has the format
|
|
176
|
+
// <replSetName>/<hostname1><:port>,<hostname2><:port>,<...>
|
|
177
|
+
const replSetHostMatch = (options.host ?? '').match(
|
|
178
|
+
/^(?<replSetName>[^/]+)\/(?<hosts>([A-Za-z0-9._-]+(:\d+)?,?)+)$/
|
|
179
|
+
);
|
|
180
|
+
if (replSetHostMatch) {
|
|
181
|
+
const { replSetName, hosts } = replSetHostMatch.groups as { replSetName: string, hosts: string };
|
|
182
|
+
const connectionString = new ConnectionString(`${Scheme.Mongo}replacemeHost/${encodeURIComponent(uri || '')}`);
|
|
183
|
+
connectionString.hosts = validateHostSeedList(hosts, options.port);
|
|
184
|
+
connectionString.searchParams.set('replicaSet', replSetName);
|
|
185
|
+
return addShellConnectionStringParameters(connectionString);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// If the --host argument contains multiple hosts as a seed list
|
|
189
|
+
// we directly do not do additional host/port parsing
|
|
190
|
+
const seedList = (options.host ?? '').match(
|
|
191
|
+
/^(?<hosts>([A-Za-z0-9._-]+(:\d+)?,?)+)$/
|
|
192
|
+
);
|
|
193
|
+
if (seedList && options.host?.includes(',')) {
|
|
194
|
+
const { hosts } = seedList.groups as { hosts: string };
|
|
195
|
+
const connectionString = new ConnectionString(`${Scheme.Mongo}replacemeHost/${encodeURIComponent(uri || '')}`);
|
|
196
|
+
connectionString.hosts = validateHostSeedList(hosts, options.port);
|
|
197
|
+
return addShellConnectionStringParameters(connectionString);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// There is no URI provided, use default 127.0.0.1:27017
|
|
201
|
+
if (!uri) {
|
|
202
|
+
return new ConnectionString(`${Scheme.Mongo}${generateHost(options)}:${generatePort(options)}/?directConnection=true`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// mongodb+srv:// URI is provided, treat as correct and immediately return
|
|
206
|
+
if (uri.startsWith(Scheme.MongoSrv)) {
|
|
207
|
+
const connectionString = new ConnectionString(uri);
|
|
208
|
+
validateConflicts(options, connectionString);
|
|
209
|
+
return connectionString;
|
|
210
|
+
} else if (uri.startsWith(Scheme.Mongo)) {
|
|
211
|
+
// we need to figure out if we have to add the directConnection query parameter
|
|
212
|
+
const connectionString = new ConnectionString(uri);
|
|
213
|
+
validateConflicts(options, connectionString);
|
|
214
|
+
return addShellConnectionStringParameters(connectionString);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Capture host, port and db from the string and generate a URI from
|
|
218
|
+
// the parts. If there is a db part, it *must* start with /.
|
|
219
|
+
const uriMatch = /^([A-Za-z0-9][A-Za-z0-9._-]+):?(\d+)?(?:\/(\S*))?$/gi;
|
|
220
|
+
let parts: string[] | null = uriMatch.exec(uri);
|
|
221
|
+
|
|
222
|
+
if (parts === null) {
|
|
223
|
+
if (/[/\\. "$]/.test(uri)) {
|
|
224
|
+
// This cannot be a database name because 'uri' contains characters invalid in a database.
|
|
225
|
+
throw new MongoshInvalidInputError(`Invalid URI: ${uri}`, CommonErrors.InvalidArgument);
|
|
226
|
+
} else {
|
|
227
|
+
parts = [ uri, uri ];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let host: string | undefined = parts?.[1];
|
|
232
|
+
const port = parts?.[2];
|
|
233
|
+
let dbAndQueryString = parts?.[3];
|
|
234
|
+
|
|
235
|
+
// If there is no port and db, host becomes db if there is no
|
|
236
|
+
// '.' in the string. (legacy shell behaviour)
|
|
237
|
+
if (!port && !dbAndQueryString && host.indexOf('.') < 0) {
|
|
238
|
+
dbAndQueryString = host;
|
|
239
|
+
host = undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// If we have a host or port, validate that the options don't also
|
|
243
|
+
// have a host or port in them.
|
|
244
|
+
if (host || port) {
|
|
245
|
+
validateConflicts(options);
|
|
246
|
+
}
|
|
247
|
+
return addShellConnectionStringParameters(new ConnectionString(
|
|
248
|
+
`${Scheme.Mongo}${host || generateHost(options)}:${port || generatePort(options)}/${encodeURIComponent(dbAndQueryString || DEFAULT_DB)}`));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Adds the `directConnection=true` query parameter if required.
|
|
253
|
+
* @param uri mongodb:// connection string
|
|
254
|
+
*/
|
|
255
|
+
function addShellConnectionStringParameters(uri: ConnectionString): ConnectionString {
|
|
256
|
+
uri = uri.clone();
|
|
257
|
+
const params = uri.searchParams;
|
|
258
|
+
if (!params.has('replicaSet') && !params.has('directConnection') && !params.has('loadBalanced') && uri.hosts.length === 1) {
|
|
259
|
+
params.set('directConnection', 'true');
|
|
260
|
+
}
|
|
261
|
+
return uri;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export default generateUri;
|
|
265
|
+
export { Scheme };
|