mongodb 7.7.0 → 7.8.0
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/README.md +1 -1
- package/lib/bulk/unordered.js +1 -1
- package/lib/bulk/unordered.js.map +1 -1
- package/lib/change_stream.js +3 -11
- package/lib/change_stream.js.map +1 -1
- package/lib/client-side-encryption/client_encryption.js.map +1 -1
- package/lib/cmap/wire_protocol/compression.js +2 -0
- package/lib/cmap/wire_protocol/compression.js.map +1 -1
- package/lib/collection.js +18 -119
- package/lib/collection.js.map +1 -1
- package/lib/connection_string.js +110 -15
- package/lib/connection_string.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +9 -62
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/aggregation_cursor.js +0 -9
- package/lib/cursor/aggregation_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js +0 -9
- package/lib/cursor/find_cursor.js.map +1 -1
- package/lib/cursor/list_collections_cursor.js +1 -1
- package/lib/cursor/list_collections_cursor.js.map +1 -1
- package/lib/cursor/list_indexes_cursor.js +1 -1
- package/lib/cursor/list_indexes_cursor.js.map +1 -1
- package/lib/cursor/run_command_cursor.js.map +1 -1
- package/lib/db.js +5 -58
- package/lib/db.js.map +1 -1
- package/lib/deps.js +35 -1
- package/lib/deps.js.map +1 -1
- package/lib/explain.js +0 -8
- package/lib/explain.js.map +1 -1
- package/lib/gridfs/download.js +3 -11
- package/lib/gridfs/download.js.map +1 -1
- package/lib/gridfs/index.js +4 -4
- package/lib/gridfs/index.js.map +1 -1
- package/lib/gridfs/upload.js +3 -18
- package/lib/gridfs/upload.js.map +1 -1
- package/lib/mongo_client.js +2 -60
- package/lib/mongo_client.js.map +1 -1
- package/lib/operations/command.js +0 -2
- package/lib/operations/command.js.map +1 -1
- package/lib/operations/create_collection.js +1 -1
- package/lib/operations/create_collection.js.map +1 -1
- package/lib/operations/indexes.js +45 -16
- package/lib/operations/indexes.js.map +1 -1
- package/lib/sdam/srv_polling.js +7 -2
- package/lib/sdam/srv_polling.js.map +1 -1
- package/lib/sdam/topology.js +3 -1
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sessions.js +10 -47
- package/lib/sessions.js.map +1 -1
- package/lib/sort.js +1 -1
- package/lib/sort.js.map +1 -1
- package/lib/timeout.js +2 -11
- package/lib/timeout.js.map +1 -1
- package/lib/utils.js +84 -23
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +249 -314
- package/package.json +1 -1
- package/src/bulk/unordered.ts +1 -1
- package/src/change_stream.ts +4 -12
- package/src/client-side-encryption/client_encryption.ts +5 -32
- package/src/client-side-encryption/kms_options.ts +1 -0
- package/src/cmap/wire_protocol/compression.ts +3 -1
- package/src/collection.ts +83 -71
- package/src/connection_string.ts +135 -19
- package/src/cursor/abstract_cursor.ts +20 -97
- package/src/cursor/aggregation_cursor.ts +1 -16
- package/src/cursor/find_cursor.ts +2 -17
- package/src/cursor/list_collections_cursor.ts +1 -1
- package/src/cursor/list_indexes_cursor.ts +1 -1
- package/src/cursor/run_command_cursor.ts +4 -31
- package/src/db.ts +13 -59
- package/src/deps.ts +38 -1
- package/src/explain.ts +0 -8
- package/src/gridfs/download.ts +7 -14
- package/src/gridfs/index.ts +27 -8
- package/src/gridfs/upload.ts +6 -21
- package/src/index.ts +2 -0
- package/src/mongo_client.ts +46 -61
- package/src/operations/command.ts +1 -7
- package/src/operations/create_collection.ts +4 -2
- package/src/operations/drop.ts +1 -1
- package/src/operations/indexes.ts +213 -17
- package/src/operations/operation.ts +1 -1
- package/src/operations/run_command.ts +1 -1
- package/src/sdam/srv_polling.ts +12 -3
- package/src/sdam/topology.ts +5 -1
- package/src/sessions.ts +21 -63
- package/src/sort.ts +1 -1
- package/src/timeout.ts +2 -13
- package/src/utils.ts +96 -30
package/src/sdam/topology.ts
CHANGED
|
@@ -152,6 +152,8 @@ export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
|
|
|
152
152
|
/** The name of the replica set to connect to */
|
|
153
153
|
replicaSet?: string;
|
|
154
154
|
srvHost?: string;
|
|
155
|
+
srvAllowedHostsSuffix?: string;
|
|
156
|
+
srvHostValidator?: (host: string) => boolean;
|
|
155
157
|
srvPoller?: SrvPoller;
|
|
156
158
|
/** Indicates that a client should directly connect to a node without attempting to discover its topology type */
|
|
157
159
|
directConnection: boolean;
|
|
@@ -334,7 +336,9 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
|
|
|
334
336
|
heartbeatFrequencyMS: this.s.heartbeatFrequencyMS,
|
|
335
337
|
srvHost: options.srvHost,
|
|
336
338
|
srvMaxHosts: options.srvMaxHosts,
|
|
337
|
-
srvServiceName: options.srvServiceName
|
|
339
|
+
srvServiceName: options.srvServiceName,
|
|
340
|
+
srvAllowedHostsSuffix: options.srvAllowedHostsSuffix,
|
|
341
|
+
srvHostValidator: options.srvHostValidator
|
|
338
342
|
});
|
|
339
343
|
|
|
340
344
|
this.on(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology);
|
package/src/sessions.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { RunCommandOperation } from './operations/run_command';
|
|
|
30
30
|
import { ReadConcernLevel } from './read_concern';
|
|
31
31
|
import { ReadPreference } from './read_preference';
|
|
32
32
|
import { _advanceClusterTime, type ClusterTime, TopologyType } from './sdam/common';
|
|
33
|
-
import { TimeoutContext } from './timeout';
|
|
33
|
+
import { type TimeoutContext } from './timeout';
|
|
34
34
|
import {
|
|
35
35
|
isTransactionCommand,
|
|
36
36
|
Transaction,
|
|
@@ -60,9 +60,8 @@ export interface ClientSessionOptions {
|
|
|
60
60
|
defaultTransactionOptions?: TransactionOptions;
|
|
61
61
|
/**
|
|
62
62
|
* @public
|
|
63
|
-
* @experimental
|
|
64
63
|
* An overriding timeoutMS value to use for a client-side timeout.
|
|
65
|
-
*
|
|
64
|
+
* @deprecated `timeoutMS` has no effect and will be removed in a future release.
|
|
66
65
|
*/
|
|
67
66
|
defaultTimeoutMS?: number;
|
|
68
67
|
|
|
@@ -92,7 +91,10 @@ export interface EndSessionOptions {
|
|
|
92
91
|
force?: boolean;
|
|
93
92
|
forceClear?: boolean;
|
|
94
93
|
|
|
95
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Specifies the time an operation will run until it throws a timeout error
|
|
96
|
+
* @deprecated `timeoutMS` has no effect and will be removed in a future release.
|
|
97
|
+
*/
|
|
96
98
|
timeoutMS?: number;
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -138,8 +140,8 @@ export class ClientSession
|
|
|
138
140
|
/** @internal */
|
|
139
141
|
public txnNumberIncrement: number;
|
|
140
142
|
/**
|
|
141
|
-
* @experimental
|
|
142
143
|
* Specifies the time an operation in a given `ClientSession` will run until it throws a timeout error
|
|
144
|
+
* @deprecated `timeoutMS` has no effect and will be removed in a future release.
|
|
143
145
|
*/
|
|
144
146
|
timeoutMS?: number;
|
|
145
147
|
|
|
@@ -186,7 +188,6 @@ export class ClientSession
|
|
|
186
188
|
this.sessionPool = sessionPool;
|
|
187
189
|
this.hasEnded = false;
|
|
188
190
|
this.clientOptions = clientOptions;
|
|
189
|
-
this.timeoutMS = options.defaultTimeoutMS ?? client.s.options?.timeoutMS;
|
|
190
191
|
|
|
191
192
|
this.explicit = !!options.explicit;
|
|
192
193
|
this._serverSession = this.explicit ? this.sessionPool.acquire() : null;
|
|
@@ -424,9 +425,12 @@ export class ClientSession
|
|
|
424
425
|
/**
|
|
425
426
|
* Commits the currently active transaction in this session.
|
|
426
427
|
*
|
|
427
|
-
* @param
|
|
428
|
+
* @param _options - `timeoutMS` is deprecated and has no effect.
|
|
428
429
|
*/
|
|
429
|
-
async commitTransaction(
|
|
430
|
+
async commitTransaction(_options?: {
|
|
431
|
+
/** @deprecated `timeoutMS` has no effect and will be removed in a future release. */
|
|
432
|
+
timeoutMS?: number;
|
|
433
|
+
}): Promise<void> {
|
|
430
434
|
if (this.transaction.state === TxnState.NO_TRANSACTION) {
|
|
431
435
|
throw new MongoTransactionError('No transaction started');
|
|
432
436
|
}
|
|
@@ -453,16 +457,9 @@ export class ClientSession
|
|
|
453
457
|
maxTimeMS?: number;
|
|
454
458
|
} = { commitTransaction: 1 };
|
|
455
459
|
|
|
456
|
-
const timeoutMS =
|
|
457
|
-
typeof options?.timeoutMS === 'number'
|
|
458
|
-
? options.timeoutMS
|
|
459
|
-
: typeof this.timeoutMS === 'number'
|
|
460
|
-
? this.timeoutMS
|
|
461
|
-
: null;
|
|
462
|
-
|
|
463
460
|
const wc = this.transaction.options.writeConcern ?? this.clientOptions?.writeConcern;
|
|
464
461
|
if (wc != null) {
|
|
465
|
-
if (
|
|
462
|
+
if (this.timeoutContext == null) {
|
|
466
463
|
WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
|
|
467
464
|
} else {
|
|
468
465
|
const wcKeys = Object.keys(wc);
|
|
@@ -477,7 +474,7 @@ export class ClientSession
|
|
|
477
474
|
}
|
|
478
475
|
|
|
479
476
|
if (this.transaction.state === TxnState.TRANSACTION_COMMITTED || this.commitAttempted) {
|
|
480
|
-
if (
|
|
477
|
+
if (this.timeoutContext == null) {
|
|
481
478
|
WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
|
|
482
479
|
} else {
|
|
483
480
|
WriteConcern.apply(command, { w: 'majority', ...wc, wtimeoutMS: undefined });
|
|
@@ -499,18 +496,8 @@ export class ClientSession
|
|
|
499
496
|
});
|
|
500
497
|
operation.maxAttempts = this.clientOptions.maxAdaptiveRetries + 1;
|
|
501
498
|
|
|
502
|
-
const timeoutContext =
|
|
503
|
-
this.timeoutContext ??
|
|
504
|
-
(typeof timeoutMS === 'number'
|
|
505
|
-
? TimeoutContext.create({
|
|
506
|
-
serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
|
|
507
|
-
socketTimeoutMS: this.clientOptions.socketTimeoutMS,
|
|
508
|
-
timeoutMS
|
|
509
|
-
})
|
|
510
|
-
: null);
|
|
511
|
-
|
|
512
499
|
try {
|
|
513
|
-
await executeOperation(this.client, operation
|
|
500
|
+
await executeOperation(this.client, operation);
|
|
514
501
|
this.commitAttempted = undefined;
|
|
515
502
|
return;
|
|
516
503
|
} catch (firstCommitError) {
|
|
@@ -534,7 +521,7 @@ export class ClientSession
|
|
|
534
521
|
bypassPinningCheck: true
|
|
535
522
|
});
|
|
536
523
|
op.maxAttempts = remainingAttempts;
|
|
537
|
-
await executeOperation(this.client, op
|
|
524
|
+
await executeOperation(this.client, op);
|
|
538
525
|
return;
|
|
539
526
|
} catch (retryCommitError) {
|
|
540
527
|
// If the retry failed, we process that error instead of the original
|
|
@@ -602,26 +589,8 @@ export class ClientSession
|
|
|
602
589
|
recoveryToken?: Document;
|
|
603
590
|
} = { abortTransaction: 1 };
|
|
604
591
|
|
|
605
|
-
const timeoutMS =
|
|
606
|
-
typeof options?.timeoutMS === 'number'
|
|
607
|
-
? options.timeoutMS
|
|
608
|
-
: this.timeoutContext?.csotEnabled()
|
|
609
|
-
? this.timeoutContext.timeoutMS // refresh timeoutMS for abort operation
|
|
610
|
-
: typeof this.timeoutMS === 'number'
|
|
611
|
-
? this.timeoutMS
|
|
612
|
-
: null;
|
|
613
|
-
|
|
614
|
-
const timeoutContext =
|
|
615
|
-
timeoutMS != null
|
|
616
|
-
? TimeoutContext.create({
|
|
617
|
-
timeoutMS,
|
|
618
|
-
serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
|
|
619
|
-
socketTimeoutMS: this.clientOptions.socketTimeoutMS
|
|
620
|
-
})
|
|
621
|
-
: null;
|
|
622
|
-
|
|
623
592
|
const wc = this.transaction.options.writeConcern ?? this.clientOptions?.writeConcern;
|
|
624
|
-
if (wc != null
|
|
593
|
+
if (wc != null) {
|
|
625
594
|
WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
|
|
626
595
|
}
|
|
627
596
|
|
|
@@ -636,7 +605,7 @@ export class ClientSession
|
|
|
636
605
|
});
|
|
637
606
|
|
|
638
607
|
try {
|
|
639
|
-
await executeOperation(this.client, operation
|
|
608
|
+
await executeOperation(this.client, operation);
|
|
640
609
|
this.unpin();
|
|
641
610
|
return;
|
|
642
611
|
} catch (firstAbortError) {
|
|
@@ -649,7 +618,7 @@ export class ClientSession
|
|
|
649
618
|
|
|
650
619
|
if (firstAbortError instanceof MongoError && isRetryableWriteError(firstAbortError)) {
|
|
651
620
|
try {
|
|
652
|
-
await executeOperation(this.client, operation
|
|
621
|
+
await executeOperation(this.client, operation);
|
|
653
622
|
return;
|
|
654
623
|
} catch (secondAbortError) {
|
|
655
624
|
if (secondAbortError.name === 'MongoRuntimeError') throw secondAbortError;
|
|
@@ -716,25 +685,14 @@ export class ClientSession
|
|
|
716
685
|
options?: TransactionOptions & {
|
|
717
686
|
/**
|
|
718
687
|
* Configures a timeoutMS expiry for the entire withTransactionCallback.
|
|
719
|
-
*
|
|
720
|
-
* @remarks
|
|
721
|
-
* - The remaining timeout will not be applied to callback operations that do not use the ClientSession.
|
|
722
|
-
* - Overriding timeoutMS for operations executed using the explicit session inside the provided callback will result in a client-side error.
|
|
688
|
+
* @deprecated `timeoutMS` has no effect and will be removed in a future release.
|
|
723
689
|
*/
|
|
724
690
|
timeoutMS?: number;
|
|
725
691
|
}
|
|
726
692
|
): Promise<T> {
|
|
727
693
|
const MAX_TIMEOUT = 120_000;
|
|
728
694
|
|
|
729
|
-
|
|
730
|
-
this.timeoutContext =
|
|
731
|
-
timeoutMS != null
|
|
732
|
-
? TimeoutContext.create({
|
|
733
|
-
timeoutMS,
|
|
734
|
-
serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
|
|
735
|
-
socketTimeoutMS: this.clientOptions.socketTimeoutMS
|
|
736
|
-
})
|
|
737
|
-
: null;
|
|
695
|
+
// timeoutMS is a no-op, so a CSOT timeout context is never created
|
|
738
696
|
|
|
739
697
|
// 1. Define the following:
|
|
740
698
|
// 1.1 Record the current monotonic time, which will be used to enforce the 120-second / CSOT timeout before later retry attempts.
|
package/src/sort.ts
CHANGED
|
@@ -81,7 +81,7 @@ function isReadonlyArray<T>(value: any): value is readonly T[] {
|
|
|
81
81
|
|
|
82
82
|
/** @internal */
|
|
83
83
|
function pairToMap(v: readonly [string, SortDirection]): SortForCmd {
|
|
84
|
-
return new Map([[`${v[0]}`, prepareDirection(
|
|
84
|
+
return new Map([[`${v[0]}`, prepareDirection(v[1])]]);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/** @internal */
|
package/src/timeout.ts
CHANGED
|
@@ -150,23 +150,12 @@ function isLegacyTimeoutContextOptions(v: unknown): v is LegacyTimeoutContextOpt
|
|
|
150
150
|
);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function isCSOTTimeoutContextOptions(v: unknown): v is CSOTTimeoutContextOptions {
|
|
154
|
-
return (
|
|
155
|
-
v != null &&
|
|
156
|
-
typeof v === 'object' &&
|
|
157
|
-
'serverSelectionTimeoutMS' in v &&
|
|
158
|
-
typeof v.serverSelectionTimeoutMS === 'number' &&
|
|
159
|
-
'timeoutMS' in v &&
|
|
160
|
-
typeof v.timeoutMS === 'number'
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
153
|
/** @internal */
|
|
165
154
|
export abstract class TimeoutContext {
|
|
166
155
|
static create(options: TimeoutContextOptions): TimeoutContext {
|
|
167
156
|
if (options.session?.timeoutContext != null) return options.session?.timeoutContext;
|
|
168
|
-
|
|
169
|
-
|
|
157
|
+
// timeoutMS is a no-op, so a CSOT context is never created
|
|
158
|
+
if (isLegacyTimeoutContextOptions(options)) return new LegacyTimeoutContext(options);
|
|
170
159
|
else throw new MongoRuntimeError('Unrecognized options');
|
|
171
160
|
}
|
|
172
161
|
|
package/src/utils.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { promises as fs } from 'fs';
|
|
|
4
4
|
import * as http from 'http';
|
|
5
5
|
import * as process from 'process';
|
|
6
6
|
import { clearTimeout, setTimeout } from 'timers';
|
|
7
|
+
import { domainToASCII } from 'url';
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
ByteUtils,
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
MongoParseError,
|
|
30
31
|
MongoRuntimeError
|
|
31
32
|
} from './error';
|
|
32
|
-
import type { MongoClient } from './mongo_client';
|
|
33
|
+
import type { MongoClient, MongoOptions } from './mongo_client';
|
|
33
34
|
import { type Abortable } from './mongo_types';
|
|
34
35
|
import type { CommandOperationOptions, OperationParent } from './operations/command';
|
|
35
36
|
import type { Hint, OperationOptions } from './operations/operation';
|
|
@@ -493,12 +494,19 @@ export function resolveTimeoutOptions<T extends Partial<TimeoutContextOptions>>(
|
|
|
493
494
|
): T &
|
|
494
495
|
Pick<
|
|
495
496
|
MongoClient['s']['options'],
|
|
496
|
-
'
|
|
497
|
-
> {
|
|
498
|
-
const { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS
|
|
499
|
-
|
|
500
|
-
return {
|
|
497
|
+
'serverSelectionTimeoutMS' | 'waitQueueTimeoutMS' | 'socketTimeoutMS'
|
|
498
|
+
> & { timeoutMS?: undefined } {
|
|
499
|
+
const { socketTimeoutMS, serverSelectionTimeoutMS, waitQueueTimeoutMS } = client.s.options;
|
|
500
|
+
// timeoutMS is a no-op, so it is never forwarded
|
|
501
|
+
return {
|
|
502
|
+
socketTimeoutMS,
|
|
503
|
+
serverSelectionTimeoutMS,
|
|
504
|
+
waitQueueTimeoutMS,
|
|
505
|
+
...options,
|
|
506
|
+
timeoutMS: undefined
|
|
507
|
+
};
|
|
501
508
|
}
|
|
509
|
+
|
|
502
510
|
/**
|
|
503
511
|
* Merge inherited properties from parent into options, prioritizing values from options,
|
|
504
512
|
* then values from parent.
|
|
@@ -514,7 +522,6 @@ export function resolveOptions<T extends CommandOperationOptions>(
|
|
|
514
522
|
): T {
|
|
515
523
|
const result: T = Object.assign({}, options, resolveBSONOptions(options, parent));
|
|
516
524
|
|
|
517
|
-
const timeoutMS = options?.timeoutMS ?? parent?.timeoutMS;
|
|
518
525
|
// Users cannot pass a readConcern/writeConcern to operations in a transaction
|
|
519
526
|
const session = options?.session;
|
|
520
527
|
|
|
@@ -524,35 +531,20 @@ export function resolveOptions<T extends CommandOperationOptions>(
|
|
|
524
531
|
result.readConcern = readConcern;
|
|
525
532
|
}
|
|
526
533
|
|
|
527
|
-
|
|
534
|
+
const writeConcern = WriteConcern.fromOptions(options) ?? parent?.writeConcern;
|
|
528
535
|
if (writeConcern) {
|
|
529
|
-
if (timeoutMS != null) {
|
|
530
|
-
writeConcern = WriteConcern.fromOptions({
|
|
531
|
-
writeConcern: {
|
|
532
|
-
...writeConcern,
|
|
533
|
-
wtimeout: undefined,
|
|
534
|
-
wtimeoutMS: undefined
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
536
|
result.writeConcern = writeConcern;
|
|
539
537
|
}
|
|
540
538
|
}
|
|
541
539
|
|
|
542
|
-
|
|
540
|
+
// timeoutMS is a no-op, so it is never propagated to the operation
|
|
541
|
+
result.timeoutMS = undefined;
|
|
543
542
|
|
|
544
543
|
const readPreference = ReadPreference.fromOptions(options) ?? parent?.readPreference;
|
|
545
544
|
if (readPreference) {
|
|
546
545
|
result.readPreference = readPreference;
|
|
547
546
|
}
|
|
548
547
|
|
|
549
|
-
const isConvenientTransaction = session?.explicit && session?.timeoutContext != null;
|
|
550
|
-
if (isConvenientTransaction && options?.timeoutMS != null) {
|
|
551
|
-
throw new MongoInvalidArgumentError(
|
|
552
|
-
'An operation cannot be given a timeoutMS setting when inside a withTransaction call that has a timeoutMS setting'
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
548
|
return result;
|
|
557
549
|
}
|
|
558
550
|
|
|
@@ -1151,14 +1143,12 @@ export function parseUnsignedInteger(value: unknown): number | null {
|
|
|
1151
1143
|
* If a DNS server were to become compromised SRV records would still need to
|
|
1152
1144
|
* advertise addresses that are under the same domain as the srvHost.
|
|
1153
1145
|
*
|
|
1154
|
-
* @param address - The address to check against a domain
|
|
1146
|
+
* @param address - The address to check against a domain, already normalized with {@link normalizeDnsName}
|
|
1155
1147
|
* @param srvHost - The domain to check the provided address against
|
|
1156
1148
|
* @returns void
|
|
1157
1149
|
*/
|
|
1158
|
-
export function checkParentDomainMatch(
|
|
1159
|
-
|
|
1160
|
-
const normalizedAddress = address.endsWith('.') ? address.slice(0, address.length - 1) : address;
|
|
1161
|
-
const normalizedSrvHost = srvHost.endsWith('.') ? srvHost.slice(0, srvHost.length - 1) : srvHost;
|
|
1150
|
+
export function checkParentDomainMatch(normalizedAddress: string, srvHost: string): void {
|
|
1151
|
+
const normalizedSrvHost = normalizeDnsName(srvHost);
|
|
1162
1152
|
|
|
1163
1153
|
const allCharacterBeforeFirstDot = /^.*?\./;
|
|
1164
1154
|
const srvIsLessThanThreeParts = normalizedSrvHost.split('.').length < 3;
|
|
@@ -1187,6 +1177,82 @@ export function checkParentDomainMatch(address: string, srvHost: string): void {
|
|
|
1187
1177
|
}
|
|
1188
1178
|
}
|
|
1189
1179
|
|
|
1180
|
+
/**
|
|
1181
|
+
* Forbidden domain code points, per the WHATWG URL Standard. `domainToASCII` parses its input as
|
|
1182
|
+
* URL text, so names containing these characters are rejected before conversion.
|
|
1183
|
+
*/
|
|
1184
|
+
// eslint-disable-next-line no-control-regex
|
|
1185
|
+
const FORBIDDEN_DOMAIN_CODE_POINTS = /[\u0000-\u0020\u007F#%/:<>?@[\\\]^|]/;
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* Converts a DNS name to its lowercase A-label (Punycode) form, using the WHATWG URL Standard's
|
|
1189
|
+
* domain parser (with beStrict false).
|
|
1190
|
+
*
|
|
1191
|
+
* @returns the converted name, or `null` if the name cannot be converted
|
|
1192
|
+
*/
|
|
1193
|
+
export function dnsNameToASCII(name: string): string | null {
|
|
1194
|
+
if (FORBIDDEN_DOMAIN_CODE_POINTS.test(name)) {
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
// The standard returns an ASCII name lowercased, even if its `xn--` labels are not valid A-labels
|
|
1198
|
+
// (https://github.com/whatwg/url/commit/a8d5ca3716c3). Node.js only does so from 24.20.0, which
|
|
1199
|
+
// updated Ada to 4.0.0, so it is done here to behave the same on every Node.js version.
|
|
1200
|
+
const ascii = /^\p{ASCII}*$/u.test(name) ? name.toLowerCase() : domainToASCII(name);
|
|
1201
|
+
return ascii === '' ? null : ascii;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Normalizes a DNS host name so it can be compared against another normalized host name, as
|
|
1206
|
+
* required by the Initial DNS Seedlist Discovery specification: a trailing `.` is removed, and the
|
|
1207
|
+
* name is converted to its lowercase A-label (Punycode) form.
|
|
1208
|
+
*
|
|
1209
|
+
* @throws MongoAPIError if the name is not a valid DNS host name
|
|
1210
|
+
*/
|
|
1211
|
+
export function normalizeDnsName(name: string): string {
|
|
1212
|
+
const withoutTrailingDot = name.endsWith('.') ? name.slice(0, -1) : name;
|
|
1213
|
+
const normalized = dnsNameToASCII(withoutTrailingDot);
|
|
1214
|
+
if (normalized == null) {
|
|
1215
|
+
throw new MongoAPIError('Invalid DNS host name');
|
|
1216
|
+
}
|
|
1217
|
+
return normalized;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Verifies a host name returned by an SRV lookup. Exactly one verification applies:
|
|
1222
|
+
* - with `srvHostValidator`, the validator's return value is the complete verdict
|
|
1223
|
+
* - with `srvAllowedHostsSuffix`, the host must end with the (already normalized) suffix
|
|
1224
|
+
* - otherwise, the host must share the parent domain of `srvHost`, see {@link checkParentDomainMatch}
|
|
1225
|
+
*
|
|
1226
|
+
* @param host - A host name returned by an SRV lookup, already normalized with {@link normalizeDnsName}
|
|
1227
|
+
* @param srvHost - The host from the `mongodb+srv` connection string
|
|
1228
|
+
* @throws MongoAPIError if the host fails verification, or the validator throws or returns a
|
|
1229
|
+
* non-boolean value (the `srvHostValidator` option transform wraps the validator to throw in those
|
|
1230
|
+
* cases)
|
|
1231
|
+
*/
|
|
1232
|
+
export function verifySrvHost(
|
|
1233
|
+
host: string,
|
|
1234
|
+
srvHost: string,
|
|
1235
|
+
options: Pick<MongoOptions, 'srvAllowedHostsSuffix' | 'srvHostValidator'>
|
|
1236
|
+
): void {
|
|
1237
|
+
const { srvAllowedHostsSuffix, srvHostValidator } = options;
|
|
1238
|
+
|
|
1239
|
+
if (srvHostValidator != null) {
|
|
1240
|
+
if (!srvHostValidator(host)) {
|
|
1241
|
+
throw new MongoAPIError('Server record was rejected by srvHostValidator');
|
|
1242
|
+
}
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
if (srvAllowedHostsSuffix != null) {
|
|
1247
|
+
if (!host.endsWith(srvAllowedHostsSuffix)) {
|
|
1248
|
+
throw new MongoAPIError('Server record does not end with srvAllowedHostsSuffix');
|
|
1249
|
+
}
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
checkParentDomainMatch(host, srvHost);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1190
1256
|
/**
|
|
1191
1257
|
* Perform a get request that returns status and body.
|
|
1192
1258
|
* @internal
|