Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const bsonlib = () => {
Decimal128,
BSONSymbol,
BSONRegExp,
UUID,
BSON,
} = driver;
return {
Expand All @@ -126,6 +127,7 @@ const bsonlib = () => {
BSONSymbol,
calculateObjectSize: BSON.calculateObjectSize,
EJSON: BSON.EJSON,
UUID,
BSONRegExp,
};
};
Expand Down
67 changes: 47 additions & 20 deletions packages/shell-api/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
MongoshInvalidInputError,
MongoshUnimplementedError,
} from '@mongosh/errors';
import crypto from 'crypto';
import type { Database } from './database';
import type { Collection } from './collection';
import type { CursorIterationResult } from './result';
Expand All @@ -27,8 +26,12 @@ import { shellApiType } from './enums';
import type { AbstractFiniteCursor } from './abstract-cursor';
import type ChangeStreamCursor from './change-stream-cursor';
import type { BSON, ShellBson } from '@mongosh/shell-bson';
import { inspect } from 'util';
import type { MQLPipeline, MQLQuery } from './mql-types';
import type { InspectOptions } from 'util';

let coreUtilInspect: ((obj: any, options: InspectOptions) => string) & {
defaultOptions: InspectOptions;
};

/**
* Helper method to adapt aggregation pipeline options.
Expand Down Expand Up @@ -202,6 +205,19 @@ export function processDigestPassword(
CommonErrors.InvalidArgument
);
}
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
let crypto: typeof import('crypto');
try {
// Try to dynamically import crypto so that we don't break plain-JS-runtime builds.
// The Web Crypto API does not provide MD5, which is reasonable for a modern API
// but means that we cannot use it as a fallback.
crypto = require('crypto');
} catch {
throw new MongoshUnimplementedError(
'Legacy password digest algorithms like SCRAM-SHA-1 are not supported by this instance of mongosh',
CommonErrors.Deprecated
);
}
// NOTE: this code has raised a code scanning alert about the "use of a broken or weak cryptographic algorithm":
// we inherited this code from `mongo`, and we cannot replace MD5 with a different algorithm, since MD5 is part of the SCRAM-SHA-1 protocol,
// and the purpose of `passwordDigestor=client` is to improve the security of SCRAM-SHA-1, allowing the creation of new users
Expand Down Expand Up @@ -630,24 +646,35 @@ export async function getPrintableShardStatus(
'on shard': chunk.shard,
'last modified': chunk.lastmod,
} as any;
// Displaying a full, multi-line output for each chunk is a bit verbose,
// even if there are only a few chunks. Where supported, we use a custom
// inspection function to inspect a copy of this object with an unlimited
// line break length (i.e. all objects on a single line).
Object.defineProperty(
c,
Symbol.for('nodejs.util.inspect.custom'),
{
value: function (depth: number, options: any): string {
return inspect(
{ ...this },
{ ...options, breakLength: Infinity }
);
},
writable: true,
configurable: true,
}
);
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
coreUtilInspect ??= require('util').inspect;
} catch {
// No util.inspect available, e.g. in browser builds.
}
if (coreUtilInspect) {
// Displaying a full, multi-line output for each chunk is a bit verbose,
// even if there are only a few chunks. Where supported, we use a custom
// inspection function to inspect a copy of this object with an unlimited
// line break length (i.e. all objects on a single line).
Object.defineProperty(
c,
Symbol.for('nodejs.util.inspect.custom'),
{
value: function (
depth: number,
options: InspectOptions
): string {
return coreUtilInspect(
{ ...this },
{ ...options, breakLength: Infinity }
);
},
writable: true,
configurable: true,
}
);
}
if (chunk.jumbo) c.jumbo = 'yes';
chunksRes.push(c);
} else if (chunksRes.length === 20 && !verbose) {
Expand Down
2 changes: 1 addition & 1 deletion packages/shell-api/src/runtime-independence.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('Runtime independence', function () {
// for other environments, but which we should still ideally remove in the
// long run (and definitely not add anything here).
// Guaranteed bonusly for anyone who removes a package from this list!
const allowedNodeBuiltins = ['crypto', 'util', 'events', 'path'];
const allowedNodeBuiltins = ['events', 'path'];
// Our TextDecoder/TextEncoder polyfills require this, unfortunately.
context.Buffer = Buffer;

Expand Down
3 changes: 1 addition & 2 deletions packages/shell-api/src/shell-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
MongoshInternalError,
} from '@mongosh/errors';
import { DBQuery } from './dbquery';
import { promisify } from 'util';
import type { ClientSideFieldLevelEncryptionOptions } from './field-level-encryption';
import { dirname } from 'path';
import { ShellUserConfig } from '@mongosh/types';
Expand Down Expand Up @@ -422,7 +421,7 @@ export default class ShellApi extends ShellApiClass {

@returnsPromise
async sleep(ms: number): Promise<void> {
return await promisify(setTimeout)(ms);
return await new Promise<void>((resolve) => setTimeout(resolve, ms));
}

private async _print(
Expand Down
3 changes: 3 additions & 0 deletions packages/shell-bson/src/bson-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
calculateObjectSize,
Double,
EJSON,
UUID,
BSONRegExp,
} from 'bson';
export type {
Expand All @@ -29,6 +30,7 @@ export type {
Binary,
Double,
EJSON,
UUID,
BSONRegExp,
calculateObjectSize,
};
Expand All @@ -45,6 +47,7 @@ export type BSON = {
Binary: typeof Binary;
Double: typeof Double;
EJSON: typeof EJSON;
UUID: typeof UUID;
BSONRegExp: typeof BSONRegExp;
BSONSymbol: typeof BSONSymbol;
calculateObjectSize: typeof calculateObjectSize;
Expand Down
44 changes: 33 additions & 11 deletions packages/shell-bson/src/printable-bson.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,40 @@
import type { BSON } from './';
import type { InspectOptionsStylized, CustomInspectFunction } from 'util';
import { inspect as utilInspect } from 'util';
import type {
InspectOptionsStylized,
CustomInspectFunction,
InspectOptions,
} from 'util';
const inspectCustom = Symbol.for('nodejs.util.inspect.custom');
type BSONClassKey = BSON[Exclude<
keyof BSON,
'EJSON' | 'calculateObjectSize'
>]['prototype']['_bsontype'];

let coreUtilInspect: ((obj: any, options: InspectOptions) => string) & {
defaultOptions: InspectOptions;
};
function inspectTypedArray(
obj: Iterable<number>,
options: InspectOptions
): string {
try {
coreUtilInspect ??= require('util').inspect;
return coreUtilInspect(obj, {
...options,
// These arrays can be very large, so would prefer to use the default options instead.
maxArrayLength: coreUtilInspect.defaultOptions.maxArrayLength,
});
} catch {
const arr = Array.from(obj);
if (arr.length > 100) {
return `[${arr.slice(0, 100).join(', ')}, ... ${
arr.length - 100
} more items]`;
}
return `[${arr.join(', ')}]`;
}
}

// Turn e.g. 'new Double(...)' into 'Double(...)' but preserve possible leading whitespace
function removeNewFromInspectResult(str: string): string {
return str.replace(/^(\s*)(new )/, '$1');
Expand Down Expand Up @@ -43,30 +71,24 @@ const makeBinaryVectorInspect = (bsonLibrary: BSON): CustomInspectFunction => {
switch (this.buffer[0]) {
case bsonLibrary.Binary.VECTOR_TYPE.Int8:
return `Binary.fromInt8Array(new Int8Array(${removeTypedArrayPrefixFromInspectResult(
utilInspect(this.toInt8Array(), {
inspectTypedArray(this.toInt8Array(), {
depth,
...options,
// These arrays can be very large, so would prefer to use the default options instead.
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
})
)}))`;
case bsonLibrary.Binary.VECTOR_TYPE.Float32:
return `Binary.fromFloat32Array(new Float32Array(${removeTypedArrayPrefixFromInspectResult(
utilInspect(this.toFloat32Array(), {
inspectTypedArray(this.toFloat32Array(), {
depth,
...options,
// These arrays can be very large, so would prefer to use the default options instead.
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
})
)}))`;
case bsonLibrary.Binary.VECTOR_TYPE.PackedBit: {
const paddingInfo = this.buffer[1] === 0 ? '' : `, ${this.buffer[1]}`;
return `Binary.fromPackedBits(new Uint8Array(${removeTypedArrayPrefixFromInspectResult(
utilInspect(this.toPackedBits(), {
inspectTypedArray(this.toPackedBits(), {
depth,
...options,
// These arrays can be very large, so would prefer to use the default options instead.
maxArrayLength: utilInspect.defaultOptions.maxArrayLength,
})
)})${paddingInfo})`;
}
Expand Down
10 changes: 4 additions & 6 deletions packages/shell-bson/src/shell-bson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
assignAll,
pickWithExactKeyMatch,
} from './helpers';
import { randomBytes } from 'crypto';

type LongWithoutAccidentallyExposedMethods = Omit<
typeof Long,
Expand Down Expand Up @@ -327,11 +326,10 @@ export function constructShellBson<
UUID: assignAll(
function UUID(hexstr?: string): BinaryType {
if (hexstr === undefined) {
// Generate a version 4, variant 1 UUID, like the old shell did.
const uuid = randomBytes(16);
uuid[6] = (uuid[6] & 0x0f) | 0x40;
uuid[8] = (uuid[8] & 0x3f) | 0x80;
hexstr = uuid.toString('hex');
// TODO(MONGOSH-2710): Actually use UUID instances from `bson`
// (but then also be consistent about that when we e.g. receive
// them from the server).
hexstr = new bson.UUID().toString();
}
assertArgsDefinedType([hexstr], ['string'], 'UUID');
// Strip any dashes, as they occur in the standard UUID formatting
Expand Down
Loading