UNPKG

tedious

Version:

A TDS driver, for connecting to MS SQLServer databases.

1,384 lines (1,298 loc) 383 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _crypto = _interopRequireDefault(require("crypto")); var _os = _interopRequireDefault(require("os")); var tls = _interopRequireWildcard(require("tls")); var net = _interopRequireWildcard(require("net")); var _dns = _interopRequireDefault(require("dns")); var _constants = _interopRequireDefault(require("constants")); var _stream = require("stream"); var _identity = require("@azure/identity"); var _bulkLoad = _interopRequireDefault(require("./bulk-load")); var _debug = _interopRequireDefault(require("./debug")); var _events = require("events"); var _instanceLookup = require("./instance-lookup"); var _transientErrorLookup = require("./transient-error-lookup"); var _packet = require("./packet"); var _preloginPayload = _interopRequireDefault(require("./prelogin-payload")); var _login7Payload = _interopRequireDefault(require("./login7-payload")); var _ntlmPayload = _interopRequireDefault(require("./ntlm-payload")); var _request = _interopRequireDefault(require("./request")); var _rpcrequestPayload = _interopRequireDefault(require("./rpcrequest-payload")); var _sqlbatchPayload = _interopRequireDefault(require("./sqlbatch-payload")); var _messageIo = _interopRequireDefault(require("./message-io")); var _tokenStreamParser = require("./token/token-stream-parser"); var _transaction = require("./transaction"); var _errors = require("./errors"); var _connector = require("./connector"); var _library = require("./library"); var _tdsVersions = require("./tds-versions"); var _message = _interopRequireDefault(require("./message")); var _ntlm = require("./ntlm"); var _dataType = require("./data-type"); var _bulkLoadPayload = require("./bulk-load-payload"); var _specialStoredProcedure = _interopRequireDefault(require("./special-stored-procedure")); var _package = require("../package.json"); var _url = require("url"); var _handler = require("./token/handler"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // eslint-disable-next-line @typescript-eslint/no-unused-vars /** * @private */ const KEEP_ALIVE_INITIAL_DELAY = 30 * 1000; /** * @private */ const DEFAULT_CONNECT_TIMEOUT = 15 * 1000; /** * @private */ const DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000; /** * @private */ const DEFAULT_CANCEL_TIMEOUT = 5 * 1000; /** * @private */ const DEFAULT_CONNECT_RETRY_INTERVAL = 500; /** * @private */ const DEFAULT_PACKET_SIZE = 4 * 1024; /** * @private */ const DEFAULT_TEXTSIZE = 2147483647; /** * @private */ const DEFAULT_DATEFIRST = 7; /** * @private */ const DEFAULT_PORT = 1433; /** * @private */ const DEFAULT_TDS_VERSION = '7_4'; /** * @private */ const DEFAULT_LANGUAGE = 'us_english'; /** * @private */ const DEFAULT_DATEFORMAT = 'mdy'; /** * @private */ /** * @private */ const CLEANUP_TYPE = { NORMAL: 0, REDIRECT: 1, RETRY: 2 }; /** * A [[Connection]] instance represents a single connection to a database server. * * ```js * var Connection = require('tedious').Connection; * var config = { * "authentication": { * ..., * "options": {...} * }, * "options": {...} * }; * var connection = new Connection(config); * ``` * * Only one request at a time may be executed on a connection. Once a [[Request]] * has been initiated (with [[Connection.callProcedure]], [[Connection.execSql]], * or [[Connection.execSqlBatch]]), another should not be initiated until the * [[Request]]'s completion callback is called. */ class Connection extends _events.EventEmitter { /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ _cancelAfterRequestSent; /** * @private */ /** * Note: be aware of the different options field: * 1. config.authentication.options * 2. config.options * * ```js * const { Connection } = require('tedious'); * * const config = { * "authentication": { * ..., * "options": {...} * }, * "options": {...} * }; * * const connection = new Connection(config); * ``` * * @param config */ constructor(config) { super(); if (typeof config !== 'object' || config === null) { throw new TypeError('The "config" argument is required and must be of type Object.'); } if (typeof config.server !== 'string') { throw new TypeError('The "config.server" property is required and must be of type string.'); } this.fedAuthRequired = false; let authentication; if (config.authentication !== undefined) { if (typeof config.authentication !== 'object' || config.authentication === null) { throw new TypeError('The "config.authentication" property must be of type Object.'); } const type = config.authentication.type; const options = config.authentication.options === undefined ? {} : config.authentication.options; if (typeof type !== 'string') { throw new TypeError('The "config.authentication.type" property must be of type string.'); } if (type !== 'default' && type !== 'ntlm' && type !== 'azure-active-directory-password' && type !== 'azure-active-directory-access-token' && type !== 'azure-active-directory-msi-vm' && type !== 'azure-active-directory-msi-app-service' && type !== 'azure-active-directory-service-principal-secret' && type !== 'azure-active-directory-default') { throw new TypeError('The "type" property must one of "default", "ntlm", "azure-active-directory-password", "azure-active-directory-access-token", "azure-active-directory-default", "azure-active-directory-msi-vm" or "azure-active-directory-msi-app-service" or "azure-active-directory-service-principal-secret".'); } if (typeof options !== 'object' || options === null) { throw new TypeError('The "config.authentication.options" property must be of type object.'); } if (type === 'ntlm') { if (typeof options.domain !== 'string') { throw new TypeError('The "config.authentication.options.domain" property must be of type string.'); } if (options.userName !== undefined && typeof options.userName !== 'string') { throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); } if (options.password !== undefined && typeof options.password !== 'string') { throw new TypeError('The "config.authentication.options.password" property must be of type string.'); } authentication = { type: 'ntlm', options: { userName: options.userName, password: options.password, domain: options.domain && options.domain.toUpperCase() } }; } else if (type === 'azure-active-directory-password') { if (typeof options.clientId !== 'string') { throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); } if (options.userName !== undefined && typeof options.userName !== 'string') { throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); } if (options.password !== undefined && typeof options.password !== 'string') { throw new TypeError('The "config.authentication.options.password" property must be of type string.'); } if (options.tenantId !== undefined && typeof options.tenantId !== 'string') { throw new TypeError('The "config.authentication.options.tenantId" property must be of type string.'); } authentication = { type: 'azure-active-directory-password', options: { userName: options.userName, password: options.password, tenantId: options.tenantId, clientId: options.clientId } }; } else if (type === 'azure-active-directory-access-token') { if (typeof options.token !== 'string') { throw new TypeError('The "config.authentication.options.token" property must be of type string.'); } authentication = { type: 'azure-active-directory-access-token', options: { token: options.token } }; } else if (type === 'azure-active-directory-msi-vm') { if (options.clientId !== undefined && typeof options.clientId !== 'string') { throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); } authentication = { type: 'azure-active-directory-msi-vm', options: { clientId: options.clientId } }; } else if (type === 'azure-active-directory-default') { if (options.clientId !== undefined && typeof options.clientId !== 'string') { throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); } authentication = { type: 'azure-active-directory-default', options: { clientId: options.clientId } }; } else if (type === 'azure-active-directory-msi-app-service') { if (options.clientId !== undefined && typeof options.clientId !== 'string') { throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); } authentication = { type: 'azure-active-directory-msi-app-service', options: { clientId: options.clientId } }; } else if (type === 'azure-active-directory-service-principal-secret') { if (typeof options.clientId !== 'string') { throw new TypeError('The "config.authentication.options.clientId" property must be of type string.'); } if (typeof options.clientSecret !== 'string') { throw new TypeError('The "config.authentication.options.clientSecret" property must be of type string.'); } if (typeof options.tenantId !== 'string') { throw new TypeError('The "config.authentication.options.tenantId" property must be of type string.'); } authentication = { type: 'azure-active-directory-service-principal-secret', options: { clientId: options.clientId, clientSecret: options.clientSecret, tenantId: options.tenantId } }; } else { if (options.userName !== undefined && typeof options.userName !== 'string') { throw new TypeError('The "config.authentication.options.userName" property must be of type string.'); } if (options.password !== undefined && typeof options.password !== 'string') { throw new TypeError('The "config.authentication.options.password" property must be of type string.'); } authentication = { type: 'default', options: { userName: options.userName, password: options.password } }; } } else { authentication = { type: 'default', options: { userName: undefined, password: undefined } }; } this.config = { server: config.server, authentication: authentication, options: { abortTransactionOnError: false, appName: undefined, camelCaseColumns: false, cancelTimeout: DEFAULT_CANCEL_TIMEOUT, columnEncryptionKeyCacheTTL: 2 * 60 * 60 * 1000, // Units: milliseconds columnEncryptionSetting: false, columnNameReplacer: undefined, connectionRetryInterval: DEFAULT_CONNECT_RETRY_INTERVAL, connectTimeout: DEFAULT_CONNECT_TIMEOUT, connector: undefined, connectionIsolationLevel: _transaction.ISOLATION_LEVEL.READ_COMMITTED, cryptoCredentialsDetails: {}, database: undefined, datefirst: DEFAULT_DATEFIRST, dateFormat: DEFAULT_DATEFORMAT, debug: { data: false, packet: false, payload: false, token: false }, enableAnsiNull: true, enableAnsiNullDefault: true, enableAnsiPadding: true, enableAnsiWarnings: true, enableArithAbort: true, enableConcatNullYieldsNull: true, enableCursorCloseOnCommit: null, enableImplicitTransactions: false, enableNumericRoundabort: false, enableQuotedIdentifier: true, encrypt: true, fallbackToDefaultDb: false, encryptionKeyStoreProviders: undefined, instanceName: undefined, isolationLevel: _transaction.ISOLATION_LEVEL.READ_COMMITTED, language: DEFAULT_LANGUAGE, localAddress: undefined, maxRetriesOnTransientErrors: 3, multiSubnetFailover: false, packetSize: DEFAULT_PACKET_SIZE, port: DEFAULT_PORT, readOnlyIntent: false, requestTimeout: DEFAULT_CLIENT_REQUEST_TIMEOUT, rowCollectionOnDone: false, rowCollectionOnRequestCompletion: false, serverName: undefined, serverSupportsColumnEncryption: false, tdsVersion: DEFAULT_TDS_VERSION, textsize: DEFAULT_TEXTSIZE, trustedServerNameAE: undefined, trustServerCertificate: false, useColumnNames: false, useUTC: true, workstationId: undefined, lowerCaseGuids: false } }; if (config.options) { if (config.options.port && config.options.instanceName) { throw new Error('Port and instanceName are mutually exclusive, but ' + config.options.port + ' and ' + config.options.instanceName + ' provided'); } if (config.options.abortTransactionOnError !== undefined) { if (typeof config.options.abortTransactionOnError !== 'boolean' && config.options.abortTransactionOnError !== null) { throw new TypeError('The "config.options.abortTransactionOnError" property must be of type string or null.'); } this.config.options.abortTransactionOnError = config.options.abortTransactionOnError; } if (config.options.appName !== undefined) { if (typeof config.options.appName !== 'string') { throw new TypeError('The "config.options.appName" property must be of type string.'); } this.config.options.appName = config.options.appName; } if (config.options.camelCaseColumns !== undefined) { if (typeof config.options.camelCaseColumns !== 'boolean') { throw new TypeError('The "config.options.camelCaseColumns" property must be of type boolean.'); } this.config.options.camelCaseColumns = config.options.camelCaseColumns; } if (config.options.cancelTimeout !== undefined) { if (typeof config.options.cancelTimeout !== 'number') { throw new TypeError('The "config.options.cancelTimeout" property must be of type number.'); } this.config.options.cancelTimeout = config.options.cancelTimeout; } if (config.options.columnNameReplacer) { if (typeof config.options.columnNameReplacer !== 'function') { throw new TypeError('The "config.options.cancelTimeout" property must be of type function.'); } this.config.options.columnNameReplacer = config.options.columnNameReplacer; } if (config.options.connectionIsolationLevel !== undefined) { (0, _transaction.assertValidIsolationLevel)(config.options.connectionIsolationLevel, 'config.options.connectionIsolationLevel'); this.config.options.connectionIsolationLevel = config.options.connectionIsolationLevel; } if (config.options.connectTimeout !== undefined) { if (typeof config.options.connectTimeout !== 'number') { throw new TypeError('The "config.options.connectTimeout" property must be of type number.'); } this.config.options.connectTimeout = config.options.connectTimeout; } if (config.options.connector !== undefined) { if (typeof config.options.connector !== 'function') { throw new TypeError('The "config.options.connector" property must be a function.'); } this.config.options.connector = config.options.connector; } if (config.options.cryptoCredentialsDetails !== undefined) { if (typeof config.options.cryptoCredentialsDetails !== 'object' || config.options.cryptoCredentialsDetails === null) { throw new TypeError('The "config.options.cryptoCredentialsDetails" property must be of type Object.'); } this.config.options.cryptoCredentialsDetails = config.options.cryptoCredentialsDetails; } if (config.options.database !== undefined) { if (typeof config.options.database !== 'string') { throw new TypeError('The "config.options.database" property must be of type string.'); } this.config.options.database = config.options.database; } if (config.options.datefirst !== undefined) { if (typeof config.options.datefirst !== 'number' && config.options.datefirst !== null) { throw new TypeError('The "config.options.datefirst" property must be of type number.'); } if (config.options.datefirst !== null && (config.options.datefirst < 1 || config.options.datefirst > 7)) { throw new RangeError('The "config.options.datefirst" property must be >= 1 and <= 7'); } this.config.options.datefirst = config.options.datefirst; } if (config.options.dateFormat !== undefined) { if (typeof config.options.dateFormat !== 'string' && config.options.dateFormat !== null) { throw new TypeError('The "config.options.dateFormat" property must be of type string or null.'); } this.config.options.dateFormat = config.options.dateFormat; } if (config.options.debug) { if (config.options.debug.data !== undefined) { if (typeof config.options.debug.data !== 'boolean') { throw new TypeError('The "config.options.debug.data" property must be of type boolean.'); } this.config.options.debug.data = config.options.debug.data; } if (config.options.debug.packet !== undefined) { if (typeof config.options.debug.packet !== 'boolean') { throw new TypeError('The "config.options.debug.packet" property must be of type boolean.'); } this.config.options.debug.packet = config.options.debug.packet; } if (config.options.debug.payload !== undefined) { if (typeof config.options.debug.payload !== 'boolean') { throw new TypeError('The "config.options.debug.payload" property must be of type boolean.'); } this.config.options.debug.payload = config.options.debug.payload; } if (config.options.debug.token !== undefined) { if (typeof config.options.debug.token !== 'boolean') { throw new TypeError('The "config.options.debug.token" property must be of type boolean.'); } this.config.options.debug.token = config.options.debug.token; } } if (config.options.enableAnsiNull !== undefined) { if (typeof config.options.enableAnsiNull !== 'boolean' && config.options.enableAnsiNull !== null) { throw new TypeError('The "config.options.enableAnsiNull" property must be of type boolean or null.'); } this.config.options.enableAnsiNull = config.options.enableAnsiNull; } if (config.options.enableAnsiNullDefault !== undefined) { if (typeof config.options.enableAnsiNullDefault !== 'boolean' && config.options.enableAnsiNullDefault !== null) { throw new TypeError('The "config.options.enableAnsiNullDefault" property must be of type boolean or null.'); } this.config.options.enableAnsiNullDefault = config.options.enableAnsiNullDefault; } if (config.options.enableAnsiPadding !== undefined) { if (typeof config.options.enableAnsiPadding !== 'boolean' && config.options.enableAnsiPadding !== null) { throw new TypeError('The "config.options.enableAnsiPadding" property must be of type boolean or null.'); } this.config.options.enableAnsiPadding = config.options.enableAnsiPadding; } if (config.options.enableAnsiWarnings !== undefined) { if (typeof config.options.enableAnsiWarnings !== 'boolean' && config.options.enableAnsiWarnings !== null) { throw new TypeError('The "config.options.enableAnsiWarnings" property must be of type boolean or null.'); } this.config.options.enableAnsiWarnings = config.options.enableAnsiWarnings; } if (config.options.enableArithAbort !== undefined) { if (typeof config.options.enableArithAbort !== 'boolean' && config.options.enableArithAbort !== null) { throw new TypeError('The "config.options.enableArithAbort" property must be of type boolean or null.'); } this.config.options.enableArithAbort = config.options.enableArithAbort; } if (config.options.enableConcatNullYieldsNull !== undefined) { if (typeof config.options.enableConcatNullYieldsNull !== 'boolean' && config.options.enableConcatNullYieldsNull !== null) { throw new TypeError('The "config.options.enableConcatNullYieldsNull" property must be of type boolean or null.'); } this.config.options.enableConcatNullYieldsNull = config.options.enableConcatNullYieldsNull; } if (config.options.enableCursorCloseOnCommit !== undefined) { if (typeof config.options.enableCursorCloseOnCommit !== 'boolean' && config.options.enableCursorCloseOnCommit !== null) { throw new TypeError('The "config.options.enableCursorCloseOnCommit" property must be of type boolean or null.'); } this.config.options.enableCursorCloseOnCommit = config.options.enableCursorCloseOnCommit; } if (config.options.enableImplicitTransactions !== undefined) { if (typeof config.options.enableImplicitTransactions !== 'boolean' && config.options.enableImplicitTransactions !== null) { throw new TypeError('The "config.options.enableImplicitTransactions" property must be of type boolean or null.'); } this.config.options.enableImplicitTransactions = config.options.enableImplicitTransactions; } if (config.options.enableNumericRoundabort !== undefined) { if (typeof config.options.enableNumericRoundabort !== 'boolean' && config.options.enableNumericRoundabort !== null) { throw new TypeError('The "config.options.enableNumericRoundabort" property must be of type boolean or null.'); } this.config.options.enableNumericRoundabort = config.options.enableNumericRoundabort; } if (config.options.enableQuotedIdentifier !== undefined) { if (typeof config.options.enableQuotedIdentifier !== 'boolean' && config.options.enableQuotedIdentifier !== null) { throw new TypeError('The "config.options.enableQuotedIdentifier" property must be of type boolean or null.'); } this.config.options.enableQuotedIdentifier = config.options.enableQuotedIdentifier; } if (config.options.encrypt !== undefined) { if (typeof config.options.encrypt !== 'boolean') { if (config.options.encrypt !== 'strict') { throw new TypeError('The "encrypt" property must be set to "strict", or of type boolean.'); } } this.config.options.encrypt = config.options.encrypt; } if (config.options.fallbackToDefaultDb !== undefined) { if (typeof config.options.fallbackToDefaultDb !== 'boolean') { throw new TypeError('The "config.options.fallbackToDefaultDb" property must be of type boolean.'); } this.config.options.fallbackToDefaultDb = config.options.fallbackToDefaultDb; } if (config.options.instanceName !== undefined) { if (typeof config.options.instanceName !== 'string') { throw new TypeError('The "config.options.instanceName" property must be of type string.'); } this.config.options.instanceName = config.options.instanceName; this.config.options.port = undefined; } if (config.options.isolationLevel !== undefined) { (0, _transaction.assertValidIsolationLevel)(config.options.isolationLevel, 'config.options.isolationLevel'); this.config.options.isolationLevel = config.options.isolationLevel; } if (config.options.language !== undefined) { if (typeof config.options.language !== 'string' && config.options.language !== null) { throw new TypeError('The "config.options.language" property must be of type string or null.'); } this.config.options.language = config.options.language; } if (config.options.localAddress !== undefined) { if (typeof config.options.localAddress !== 'string') { throw new TypeError('The "config.options.localAddress" property must be of type string.'); } this.config.options.localAddress = config.options.localAddress; } if (config.options.multiSubnetFailover !== undefined) { if (typeof config.options.multiSubnetFailover !== 'boolean') { throw new TypeError('The "config.options.multiSubnetFailover" property must be of type boolean.'); } this.config.options.multiSubnetFailover = config.options.multiSubnetFailover; } if (config.options.packetSize !== undefined) { if (typeof config.options.packetSize !== 'number') { throw new TypeError('The "config.options.packetSize" property must be of type number.'); } this.config.options.packetSize = config.options.packetSize; } if (config.options.port !== undefined) { if (typeof config.options.port !== 'number') { throw new TypeError('The "config.options.port" property must be of type number.'); } if (config.options.port <= 0 || config.options.port >= 65536) { throw new RangeError('The "config.options.port" property must be > 0 and < 65536'); } this.config.options.port = config.options.port; this.config.options.instanceName = undefined; } if (config.options.readOnlyIntent !== undefined) { if (typeof config.options.readOnlyIntent !== 'boolean') { throw new TypeError('The "config.options.readOnlyIntent" property must be of type boolean.'); } this.config.options.readOnlyIntent = config.options.readOnlyIntent; } if (config.options.requestTimeout !== undefined) { if (typeof config.options.requestTimeout !== 'number') { throw new TypeError('The "config.options.requestTimeout" property must be of type number.'); } this.config.options.requestTimeout = config.options.requestTimeout; } if (config.options.maxRetriesOnTransientErrors !== undefined) { if (typeof config.options.maxRetriesOnTransientErrors !== 'number') { throw new TypeError('The "config.options.maxRetriesOnTransientErrors" property must be of type number.'); } if (config.options.maxRetriesOnTransientErrors < 0) { throw new TypeError('The "config.options.maxRetriesOnTransientErrors" property must be equal or greater than 0.'); } this.config.options.maxRetriesOnTransientErrors = config.options.maxRetriesOnTransientErrors; } if (config.options.connectionRetryInterval !== undefined) { if (typeof config.options.connectionRetryInterval !== 'number') { throw new TypeError('The "config.options.connectionRetryInterval" property must be of type number.'); } if (config.options.connectionRetryInterval <= 0) { throw new TypeError('The "config.options.connectionRetryInterval" property must be greater than 0.'); } this.config.options.connectionRetryInterval = config.options.connectionRetryInterval; } if (config.options.rowCollectionOnDone !== undefined) { if (typeof config.options.rowCollectionOnDone !== 'boolean') { throw new TypeError('The "config.options.rowCollectionOnDone" property must be of type boolean.'); } this.config.options.rowCollectionOnDone = config.options.rowCollectionOnDone; } if (config.options.rowCollectionOnRequestCompletion !== undefined) { if (typeof config.options.rowCollectionOnRequestCompletion !== 'boolean') { throw new TypeError('The "config.options.rowCollectionOnRequestCompletion" property must be of type boolean.'); } this.config.options.rowCollectionOnRequestCompletion = config.options.rowCollectionOnRequestCompletion; } if (config.options.tdsVersion !== undefined) { if (typeof config.options.tdsVersion !== 'string') { throw new TypeError('The "config.options.tdsVersion" property must be of type string.'); } this.config.options.tdsVersion = config.options.tdsVersion; } if (config.options.textsize !== undefined) { if (typeof config.options.textsize !== 'number' && config.options.textsize !== null) { throw new TypeError('The "config.options.textsize" property must be of type number or null.'); } if (config.options.textsize > 2147483647) { throw new TypeError('The "config.options.textsize" can\'t be greater than 2147483647.'); } else if (config.options.textsize < -1) { throw new TypeError('The "config.options.textsize" can\'t be smaller than -1.'); } this.config.options.textsize = config.options.textsize | 0; } if (config.options.trustServerCertificate !== undefined) { if (typeof config.options.trustServerCertificate !== 'boolean') { throw new TypeError('The "config.options.trustServerCertificate" property must be of type boolean.'); } this.config.options.trustServerCertificate = config.options.trustServerCertificate; } if (config.options.serverName !== undefined) { if (typeof config.options.serverName !== 'string') { throw new TypeError('The "config.options.serverName" property must be of type string.'); } this.config.options.serverName = config.options.serverName; } if (config.options.useColumnNames !== undefined) { if (typeof config.options.useColumnNames !== 'boolean') { throw new TypeError('The "config.options.useColumnNames" property must be of type boolean.'); } this.config.options.useColumnNames = config.options.useColumnNames; } if (config.options.useUTC !== undefined) { if (typeof config.options.useUTC !== 'boolean') { throw new TypeError('The "config.options.useUTC" property must be of type boolean.'); } this.config.options.useUTC = config.options.useUTC; } if (config.options.workstationId !== undefined) { if (typeof config.options.workstationId !== 'string') { throw new TypeError('The "config.options.workstationId" property must be of type string.'); } this.config.options.workstationId = config.options.workstationId; } if (config.options.lowerCaseGuids !== undefined) { if (typeof config.options.lowerCaseGuids !== 'boolean') { throw new TypeError('The "config.options.lowerCaseGuids" property must be of type boolean.'); } this.config.options.lowerCaseGuids = config.options.lowerCaseGuids; } } this.secureContextOptions = this.config.options.cryptoCredentialsDetails; if (this.secureContextOptions.secureOptions === undefined) { // If the caller has not specified their own `secureOptions`, // we set `SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS` here. // Older SQL Server instances running on older Windows versions have // trouble with the BEAST workaround in OpenSSL. // As BEAST is a browser specific exploit, we can just disable this option here. this.secureContextOptions = Object.create(this.secureContextOptions, { secureOptions: { value: _constants.default.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS } }); } this.debug = this.createDebug(); this.inTransaction = false; this.transactionDescriptors = [Buffer.from([0, 0, 0, 0, 0, 0, 0, 0])]; // 'beginTransaction', 'commitTransaction' and 'rollbackTransaction' // events are utilized to maintain inTransaction property state which in // turn is used in managing transactions. These events are only fired for // TDS version 7.2 and beyond. The properties below are used to emulate // equivalent behavior for TDS versions before 7.2. this.transactionDepth = 0; this.isSqlBatch = false; this.closed = false; this.messageBuffer = Buffer.alloc(0); this.curTransientRetryCount = 0; this.transientErrorLookup = new _transientErrorLookup.TransientErrorLookup(); this.state = this.STATE.INITIALIZED; this._cancelAfterRequestSent = () => { this.messageIo.sendMessage(_packet.TYPE.ATTENTION); this.createCancelTimer(); }; } connect(connectListener) { if (this.state !== this.STATE.INITIALIZED) { throw new _errors.ConnectionError('`.connect` can not be called on a Connection in `' + this.state.name + '` state.'); } if (connectListener) { const onConnect = err => { this.removeListener('error', onError); connectListener(err); }; const onError = err => { this.removeListener('connect', onConnect); connectListener(err); }; this.once('connect', onConnect); this.once('error', onError); } this.transitionTo(this.STATE.CONNECTING); } /** * The server has reported that the charset has changed. */ /** * The attempt to connect and validate has completed. */ /** * The server has reported that the active database has changed. * This may be as a result of a successful login, or a `use` statement. */ /** * A debug message is available. It may be logged or ignored. */ /** * Internal error occurs. */ /** * The server has issued an error message. */ /** * The connection has ended. * * This may be as a result of the client calling [[close]], the server * closing the connection, or a network error. */ /** * The server has issued an information message. */ /** * The server has reported that the language has changed. */ /** * The connection was reset. */ /** * A secure connection has been established. */ on(event, listener) { return super.on(event, listener); } /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ /** * @private */ emit(event, ...args) { return super.emit(event, ...args); } /** * Closes the connection to the database. * * The [[Event_end]] will be emitted once the connection has been closed. */ close() { this.transitionTo(this.STATE.FINAL); } /** * @private */ initialiseConnection() { const signal = this.createConnectTimer(); if (this.config.options.port) { return this.connectOnPort(this.config.options.port, this.config.options.multiSubnetFailover, signal, this.config.options.connector); } else { return (0, _instanceLookup.instanceLookup)({ server: this.config.server, instanceName: this.config.options.instanceName, timeout: this.config.options.connectTimeout, signal: signal }).then(port => { process.nextTick(() => { this.connectOnPort(port, this.config.options.multiSubnetFailover, signal, this.config.options.connector); }); }, err => { this.clearConnectTimer(); if (signal.aborted) { // Ignore the AbortError for now, this is still handled by the connectTimer firing return; } process.nextTick(() => { this.emit('connect', new _errors.ConnectionError(err.message, 'EINSTLOOKUP')); }); }); } } /** * @private */ cleanupConnection(cleanupType) { if (!this.closed) { this.clearConnectTimer(); this.clearRequestTimer(); this.clearRetryTimer(); this.closeConnection(); if (cleanupType === CLEANUP_TYPE.REDIRECT) { this.emit('rerouting'); } else if (cleanupType !== CLEANUP_TYPE.RETRY) { process.nextTick(() => { this.emit('end'); }); } const request = this.request; if (request) { const err = new _errors.RequestError('Connection closed before request completed.', 'ECLOSE'); request.callback(err); this.request = undefined; } this.closed = true; this.loginError = undefined; } } /** * @private */ createDebug() { const debug = new _debug.default(this.config.options.debug); debug.on('debug', message => { this.emit('debug', message); }); return debug; } /** * @private */ createTokenStreamParser(message, handler) { return new _tokenStreamParser.Parser(message, this.debug, handler, this.config.options); } socketHandlingForSendPreLogin(socket) { socket.on('error', error => { this.socketError(error); }); socket.on('close', () => { this.socketClose(); }); socket.on('end', () => { this.socketEnd(); }); socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); this.messageIo = new _messageIo.default(socket, this.config.options.packetSize, this.debug); this.messageIo.on('secure', cleartext => { this.emit('secure', cleartext); }); this.socket = socket; this.closed = false; this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); this.sendPreLogin(); this.transitionTo(this.STATE.SENT_PRELOGIN); } wrapWithTls(socket, signal) { signal.throwIfAborted(); return new Promise((resolve, reject) => { const secureContext = tls.createSecureContext(this.secureContextOptions); // If connect to an ip address directly, // need to set the servername to an empty string // if the user has not given a servername explicitly const serverName = !net.isIP(this.config.server) ? this.config.server : ''; const encryptOptions = { host: this.config.server, socket: socket, ALPNProtocols: ['tds/8.0'], secureContext: secureContext, servername: this.config.options.serverName ? this.config.options.serverName : serverName }; const encryptsocket = tls.connect(encryptOptions); const onAbort = () => { encryptsocket.removeListener('error', onError); encryptsocket.removeListener('connect', onConnect); encryptsocket.destroy(); reject(signal.reason); }; const onError = err => { signal.removeEventListener('abort', onAbort); encryptsocket.removeListener('error', onError); encryptsocket.removeListener('connect', onConnect); encryptsocket.destroy(); reject(err); }; const onConnect = () => { signal.removeEventListener('abort', onAbort); encryptsocket.removeListener('error', onError); encryptsocket.removeListener('connect', onConnect); resolve(encryptsocket); }; signal.addEventListener('abort', onAbort, { once: true }); encryptsocket.on('error', onError); encryptsocket.on('secureConnect', onConnect); }); } connectOnPort(port, multiSubnetFailover, signal, customConnector) { const connectOpts = { host: this.routingData ? this.routingData.server : this.config.server, port: this.routingData ? this.routingData.port : port, localAddress: this.config.options.localAddress }; const connect = customConnector || (multiSubnetFailover ? _connector.connectInParallel : _connector.connectInSequence); (async () => { let socket = await connect(connectOpts, _dns.default.lookup, signal); if (this.config.options.encrypt === 'strict') { try { // Wrap the socket with TLS for TDS 8.0 socket = await this.wrapWithTls(socket, signal); } catch (err) { socket.end(); throw err; } } this.socketHandlingForSendPreLogin(socket); })().catch(err => { this.clearConnectTimer(); if (signal.aborted) { return; } process.nextTick(() => { this.socketError(err); }); }); } /** * @private */ closeConnection() { if (this.socket) { this.socket.destroy(); } } /** * @private */ createConnectTimer() { const controller = new AbortController(); this.connectTimer = setTimeout(() => { controller.abort(); this.connectTimeout(); }, this.config.options.connectTimeout); return controller.signal; } /** * @private */ createCancelTimer() { this.clearCancelTimer(); const timeout = this.config.options.cancelTimeout; if (timeout > 0) { this.cancelTimer = setTimeout(() => { this.cancelTimeout(); }, timeout); } } /** * @private */ createRequestTimer() { this.clearRequestTimer(); // release old timer, just to be safe const request = this.request; const timeout = request.timeout !== undefined ? request.timeout : this.config.options.requestTimeout; if (timeout) { this.requestTimer = setTimeout(() => { this.requestTimeout(); }, timeout); } } /** * @private */ createRetryTimer() { this.clearRetryTimer(); this.retryTimer = setTimeout(() => { this.retryTimeout(); }, this.config.options.connectionRetryInterval); } /** * @private */ connectTimeout() { const hostPostfix = this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`; // If we have routing data stored, this connection has been redirected const server = this.routingData ? this.routingData.server : this.config.server; const port = this.routingData ? `:${this.routingData.port}` : hostPostfix; // Grab the target host from the connection configuration, and from a redirect message // otherwise, leave the message empty. const routingMessage = this.routingData ? ` (redirected from ${this.config.server}${hostPostfix})` : ''; const message = `Failed to connect to ${server}${port}${routingMessage} in ${this.config.options.connectTimeout}ms`; this.debug.log(message); this.emit('connect', new _errors.ConnectionError(message, 'ETIMEOUT')); this.connectTimer = undefined; this.dispatchEvent('connectTimeout'); } /** * @private */ cancelTimeout() { const message = `Failed to cancel request in ${this.config.options.cancelTimeout}ms`; this.debug.log(message); this.dispatchEvent('socketError', new _errors.ConnectionError(message, 'ETIMEOUT')); } /** * @private */ requestTimeout() { this.requestTimer = undefined; const request = this.request; request.cancel(); const timeout = request.timeout !== undefined ? request.timeout : this.config.options.requestTimeout; const message = 'Timeout: Request failed to complete in ' + timeout + 'ms'; request.error = new _errors.RequestError(message, 'ETIMEOUT'); } /** * @private */ retryTimeout() { this.retryTimer = undefined; this.emit('retry'); this.transitionTo(this.STATE.CONNECTING); } /** * @private */ clearConnectTimer() { if (this.connectTimer) { clearTimeout(this.connectTimer); this.connectTimer = undefined; } } /** * @private */ clearCancelTimer() { if (this.cancelTimer) { clearTimeout(this.cancelTimer); this.cancelTimer = undefined; } } /** * @private */ clearRequestTimer() { if (this.requestTimer) { clearTimeout(this.requestTimer); this.requestTimer = undefined; } } /** * @private */ clearRetryTimer() { if (this.retryTimer) { clearTimeout(this.retryTimer); this.retryTimer = undefined; } } /** * @private */ transitionTo(newState) { if (this.state === newState) { this.debug.log('State is already ' + newState.name); return; } if (this.state && this.state.exit) { this.state.exit.call(this, newState); } this.debug.log('State change: ' + (this.state ? this.state.name : 'undefined') + ' -> ' + newState.name); this.state = newState; if (this.state.enter) { this.state.enter.apply(this); } } /** * @private */ getEventHandler(eventName) { const handler = this.state.events[eventName]; if (!handler) { throw new Error(`No event '${eventName}' in state '${this.state.name}'`); } return handler; } /** * @private */ dispatchEvent(eventName, ...args) { const handler = this.state.events[eventName]; if (handler) { handler.apply(this, args); } else { this.emit('error', new Error(`No event '${eventName}' in state '${this.state.name}'`)); this.close(); } } /** * @private */ socketError(error) { if (this.state === this.STATE.CONNECTING || this.state === this.STATE.SENT_TLSSSLNEGOTIATION) { const hostPostfix = this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`; // If we have routing data stored, this connection has been redirected const server = this.routingData ? this.routingData.server : this.config.server; const port = this.routingData ? `:${this.routingData.port}` : hostPostfix; // Grab the target host from the connection configuration, and from a redirect message // otherwise, leave the message empty. const routingMessage = this.routingData ? ` (redirected from ${this.config.server}${hostPostfix})` : ''; const message = `Failed to connect to ${server}${port}${routingMessage} - ${error.message}`; this.debug.log(message); this.emit('connect', new _errors.ConnectionError(message, 'ESOCKET')); } else { const message = `Connection lost - ${error.message}`; this.debug.log(message); this.emit('error', new _errors.ConnectionError(message, 'ESOCKET')); } this.dispatchEvent('socketError', error); } /** * @private */ socketEnd() { this.debug.log('socket ended'); if (this.state !== this.STATE.FINAL) { const error = new Error('socket hang up'); error.code = 'ECONNRESET'; this.socketError(error); } } /** * @private */ socketClose() { this.debug.log('connection to ' + this.config.server + ':' + this.config.options.port + ' closed'); if (this.state === this.STATE.REROUTING) { this.debug.log('Rerouting to ' + this.routingData.server + ':' + this.routingData.port); this.dispatchEvent('reconnect'); } else if (this.state === this.STATE.TRANSIENT_FAILURE_RETRY) { const server = this.routingData ? this.routingData.server : this.config.server; const port = this.routingData ? this.routingData.port : this.config.options.port; this.debug.log('Retry after transient failure connecting to ' + server + ':' + port); this.dispatchEvent('retry'); } else { this.transitionTo(this.STATE.FINAL); } } /** * @private */ sendPreLogin() { const [, major, minor, build] = /^(\d+)\.(\d+)\.(\d+)/.exec(_package.version) ?? ['0.0.0', '0', '0', '0']; const payload = new _preloginPayload.default({ // If encrypt setting is set to 'strict', then we should have already done the encryption before calling // this function. Therefore, the encrypt will