UNPKG

quosdolorem

Version:
221 lines (196 loc) 6.83 kB
/* eslint complexity: 0*/ import { CommonErrors, MongoshInvalidInputError } from '@mongosh/errors'; import i18n from '@mongosh/i18n'; import CliOptions from './cli-options'; import { ConnectionString } from './connection-string'; import { DEFAULT_DB } from './index'; /** * URI schemes. */ enum Scheme { Mongo = 'mongodb://', MongoSrv = 'mongodb+srv://' } /** * The default host. */ const DEFAULT_HOST = '127.0.0.1'; /** * The default port. */ const DEFAULT_PORT = '27017'; /** * GSSAPI options not supported as options in Node driver, * only in the URI. */ // const GSSAPI_HOST_NAME = 'gssapiHostName'; /** * GSSAPI options not supported as options in Node driver, * only in the URI. */ // const GSSAPI_SERVICE_NAME = 'gssapiServiceName'; /** * Conflicting host/port message. */ const CONFLICT = 'cli-repl.uri-generator.no-host-port'; /** * Invalid host message. */ const INVALID_HOST = 'cli-repl.uri-generator.invalid-host'; /** * Validate conflicts in the options. * * @param {CliOptions} options - The options. */ function validateConflicts(options: CliOptions): void { if (options.host || options.port) { throw new MongoshInvalidInputError(i18n.__(CONFLICT), CommonErrors.InvalidArgument); } } /** * Perform basic validation of the --host option. * * @param {string} host - The value of the --host option. */ function validateHost(host: string): void { const invalidCharacter = host.match(/[^a-zA-Z0-9.:\[\]-]/); if (invalidCharacter) { throw new MongoshInvalidInputError( i18n.__(INVALID_HOST) + ': ' + invalidCharacter[0], CommonErrors.InvalidArgument); } } /** * Generate the host from the options or default. * * @param {CliOptions} options - The options. * * @returns {string} The host. */ function generateHost(options: CliOptions): string { if (options.host) { validateHost(options.host); if (options.host.includes(':')) { return options.host.split(':')[0]; } return options.host; } return DEFAULT_HOST; } /** * Generate the port from the options or default. * * @param {CliOptions} options - The options. * * @returns {string} The port. */ function generatePort(options: CliOptions): string { if (options.host && options.host.includes(':')) { validateHost(options.host); const port = options.host.split(':')[1]; if (!options.port || options.port === port) { return port; } throw new MongoshInvalidInputError(i18n.__(CONFLICT), CommonErrors.InvalidArgument); } return options.port ? options.port : DEFAULT_PORT; } /** * Generate a URI from the provided CLI options. * * If a full URI is provided, you cannot also specify --host or --port * * Rules from the existing Shell code: * * if nodb is set then all positional parameters are files * otherwise the first positional parameter might be a dbaddress, but * only if one of these conditions is met: * - it contains no '.' after the last appearance of '\' or '/' * - it doesn't end in '.js' and it doesn't specify a path to an existing file * * gssapiHostName?: string; // needs to go in URI * gssapiServiceName?: string; // needs to go in URI */ function generateUri(options: CliOptions): string { if (options.nodb) { return ''; } const connectionString = generateUriNormalized(options); if (connectionString.hosts.length === 1 && ['localhost', '127.0.0.1'].includes(connectionString.hosts[0].split(':')[0])) { const params = connectionString.searchParams; if (!params.has('serverSelectionTimeoutMS')) { params.set('serverSelectionTimeoutMS', '2000'); } } return connectionString.toString(); } function generateUriNormalized(options: CliOptions): ConnectionString { const uri = options._?.[0]; // If the --host argument contains /, it has the format // <replSetName>/<hostname1><:port>,<hostname2><:port>,<...> const replSetHostMatch = (options.host ?? '').match( /^(?<replSetName>[^/]+)\/(?<hosts>([A-Za-z0-9.-]+(:\d+)?,?)+)$/); if (replSetHostMatch) { const { replSetName, hosts } = replSetHostMatch.groups as { replSetName: string, hosts: string }; const connectionString = new ConnectionString(`${Scheme.Mongo}replacemeHost/${uri ?? DEFAULT_DB}`); connectionString.hosts = hosts.split(',').filter(host => host.trim()); connectionString.searchParams.set('replicaSet', replSetName); return addShellConnectionStringParameters(connectionString); } // There is no URI provided, use default 127.0.0.1:27017 if (!uri) { return new ConnectionString(`${Scheme.Mongo}${generateHost(options)}:${generatePort(options)}/?directConnection=true`); } // mongodb+srv:// URI is provided, treat as correct and immediately return if (uri.startsWith(Scheme.MongoSrv)) { validateConflicts(options); return new ConnectionString(uri); } else if (uri.startsWith(Scheme.Mongo)) { // we need to figure out if we have to add the directConnection query parameter validateConflicts(options); return addShellConnectionStringParameters(new ConnectionString(uri)); } // Capture host, port and db from the string and generate a URI from // the parts. const uriMatch = /^([A-Za-z0-9][A-Za-z0-9.-]+):?(\d+)?[\/]?(\S+)?$/gi; const parts = uriMatch.exec(uri); if (parts === null) { throw new MongoshInvalidInputError(`Invalid URI: ${uri}`, CommonErrors.InvalidArgument); } let host: string | undefined = parts[1]; const port = parts[2]; let dbAndQueryString = parts[3]; // If there is no port and db, host becomes db if there is no // '.' in the string. (legacy shell behaviour) if (!port && !dbAndQueryString && host.indexOf('.') < 0) { dbAndQueryString = host; host = undefined; } // If we have a host or port, validate that the options don't also // have a host or port in them. if (host || port) { validateConflicts(options); } return addShellConnectionStringParameters(new ConnectionString( `${Scheme.Mongo}${host || generateHost(options)}:${port || generatePort(options)}/${dbAndQueryString ?? DEFAULT_DB}`)); } /** * Adds the `directConnection=true` query parameter if required, and copy * tlsCertificateKeyFile to tlsCertificateFile if the former is set but * the latter is not. * @param uri mongodb:// connection string */ function addShellConnectionStringParameters(uri: ConnectionString): ConnectionString { uri = uri.clone(); const params = uri.searchParams; if (!params.has('replicaSet') && !params.has('directConnection') && uri.hosts.length === 1) { params.set('directConnection', 'true'); } if (!params.has('tlsCertificateFile') && params.has('tlsCertificateKeyFile')) { params.set('tlsCertificateFile', params.get('tlsCertificateKeyFile') as string); } return uri; } export default generateUri; export { Scheme };