Spaces:
Running
Running
// This file was modified by Oracle on September 21, 2021. | |
// New connection options for additional authentication factors were | |
// introduced. | |
// Multi-factor authentication capability is now enabled if one of these | |
// options is used. | |
// Modifications copyright (c) 2021, Oracle and/or its affiliates. | |
; | |
const { URL } = require('url'); | |
const ClientConstants = require('./constants/client'); | |
const Charsets = require('./constants/charsets'); | |
const { version } = require('../package.json') | |
let SSLProfiles = null; | |
const validOptions = { | |
authPlugins: 1, | |
authSwitchHandler: 1, | |
bigNumberStrings: 1, | |
charset: 1, | |
charsetNumber: 1, | |
compress: 1, | |
connectAttributes: 1, | |
connectTimeout: 1, | |
database: 1, | |
dateStrings: 1, | |
debug: 1, | |
decimalNumbers: 1, | |
enableKeepAlive: 1, | |
flags: 1, | |
host: 1, | |
insecureAuth: 1, | |
infileStreamFactory: 1, | |
isServer: 1, | |
keepAliveInitialDelay: 1, | |
localAddress: 1, | |
maxPreparedStatements: 1, | |
multipleStatements: 1, | |
namedPlaceholders: 1, | |
nestTables: 1, | |
password: 1, | |
// with multi-factor authentication, the main password (used for the first | |
// authentication factor) can be provided via password1 | |
password1: 1, | |
password2: 1, | |
password3: 1, | |
passwordSha1: 1, | |
pool: 1, | |
port: 1, | |
queryFormat: 1, | |
rowsAsArray: 1, | |
socketPath: 1, | |
ssl: 1, | |
stream: 1, | |
stringifyObjects: 1, | |
supportBigNumbers: 1, | |
timezone: 1, | |
trace: 1, | |
typeCast: 1, | |
uri: 1, | |
user: 1, | |
// These options are used for Pool | |
connectionLimit: 1, | |
maxIdle: 1, | |
idleTimeout: 1, | |
Promise: 1, | |
queueLimit: 1, | |
waitForConnections: 1 | |
}; | |
class ConnectionConfig { | |
constructor(options) { | |
if (typeof options === 'string') { | |
options = ConnectionConfig.parseUrl(options); | |
} else if (options && options.uri) { | |
const uriOptions = ConnectionConfig.parseUrl(options.uri); | |
for (const key in uriOptions) { | |
if (!Object.prototype.hasOwnProperty.call(uriOptions, key)) continue; | |
if (options[key]) continue; | |
options[key] = uriOptions[key]; | |
} | |
} | |
for (const key in options) { | |
if (!Object.prototype.hasOwnProperty.call(options, key)) continue; | |
if (validOptions[key] !== 1) { | |
// REVIEW: Should this be emitted somehow? | |
// eslint-disable-next-line no-console | |
console.error( | |
`Ignoring invalid configuration option passed to Connection: ${key}. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration option to a Connection` | |
); | |
} | |
} | |
this.isServer = options.isServer; | |
this.stream = options.stream; | |
this.host = options.host || 'localhost'; | |
this.port = (typeof options.port === 'string' ? parseInt(options.port, 10) : options.port)|| 3306; | |
this.localAddress = options.localAddress; | |
this.socketPath = options.socketPath; | |
this.user = options.user || undefined; | |
// for the purpose of multi-factor authentication, or not, the main | |
// password (used for the 1st authentication factor) can also be | |
// provided via the "password1" option | |
this.password = options.password || options.password1 || undefined; | |
this.password2 = options.password2 || undefined; | |
this.password3 = options.password3 || undefined; | |
this.passwordSha1 = options.passwordSha1 || undefined; | |
this.database = options.database; | |
this.connectTimeout = isNaN(options.connectTimeout) | |
? 10 * 1000 | |
: options.connectTimeout; | |
this.insecureAuth = options.insecureAuth || false; | |
this.infileStreamFactory = options.infileStreamFactory || undefined; | |
this.supportBigNumbers = options.supportBigNumbers || false; | |
this.bigNumberStrings = options.bigNumberStrings || false; | |
this.decimalNumbers = options.decimalNumbers || false; | |
this.dateStrings = options.dateStrings || false; | |
this.debug = options.debug; | |
this.trace = options.trace !== false; | |
this.stringifyObjects = options.stringifyObjects || false; | |
this.enableKeepAlive = options.enableKeepAlive !== false; | |
this.keepAliveInitialDelay = options.keepAliveInitialDelay || 0; | |
if ( | |
options.timezone && | |
!/^(?:local|Z|[ +-]\d\d:\d\d)$/.test(options.timezone) | |
) { | |
// strictly supports timezones specified by mysqljs/mysql: | |
// https://github.com/mysqljs/mysql#user-content-connection-options | |
// eslint-disable-next-line no-console | |
console.error( | |
`Ignoring invalid timezone passed to Connection: ${options.timezone}. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration option to a Connection` | |
); | |
// SqlStrings falls back to UTC on invalid timezone | |
this.timezone = 'Z'; | |
} else { | |
this.timezone = options.timezone || 'local'; | |
} | |
this.queryFormat = options.queryFormat; | |
this.pool = options.pool || undefined; | |
this.ssl = | |
typeof options.ssl === 'string' | |
? ConnectionConfig.getSSLProfile(options.ssl) | |
: options.ssl || false; | |
this.multipleStatements = options.multipleStatements || false; | |
this.rowsAsArray = options.rowsAsArray || false; | |
this.namedPlaceholders = options.namedPlaceholders || false; | |
this.nestTables = | |
options.nestTables === undefined ? undefined : options.nestTables; | |
this.typeCast = options.typeCast === undefined ? true : options.typeCast; | |
if (this.timezone[0] === ' ') { | |
// "+" is a url encoded char for space so it | |
// gets translated to space when giving a | |
// connection string.. | |
this.timezone = `+${this.timezone.slice(1)}`; | |
} | |
if (this.ssl) { | |
if (typeof this.ssl !== 'object') { | |
throw new TypeError( | |
`SSL profile must be an object, instead it's a ${typeof this.ssl}` | |
); | |
} | |
// Default rejectUnauthorized to true | |
this.ssl.rejectUnauthorized = this.ssl.rejectUnauthorized !== false; | |
} | |
this.maxPacketSize = 0; | |
this.charsetNumber = options.charset | |
? ConnectionConfig.getCharsetNumber(options.charset) | |
: options.charsetNumber || Charsets.UTF8MB4_UNICODE_CI; | |
this.compress = options.compress || false; | |
this.authPlugins = options.authPlugins; | |
this.authSwitchHandler = options.authSwitchHandler; | |
this.clientFlags = ConnectionConfig.mergeFlags( | |
ConnectionConfig.getDefaultFlags(options), | |
options.flags || '' | |
); | |
// Default connection attributes | |
// https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html | |
const defaultConnectAttributes = { | |
_client_name: 'Node-MySQL-2', | |
_client_version: version | |
}; | |
this.connectAttributes = { ...defaultConnectAttributes, ...(options.connectAttributes || {})}; | |
this.maxPreparedStatements = options.maxPreparedStatements || 16000; | |
} | |
static mergeFlags(default_flags, user_flags) { | |
let flags = 0x0, | |
i; | |
if (!Array.isArray(user_flags)) { | |
user_flags = String(user_flags || '') | |
.toUpperCase() | |
.split(/\s*,+\s*/); | |
} | |
// add default flags unless "blacklisted" | |
for (i in default_flags) { | |
if (user_flags.indexOf(`-${default_flags[i]}`) >= 0) { | |
continue; | |
} | |
flags |= ClientConstants[default_flags[i]] || 0x0; | |
} | |
// add user flags unless already already added | |
for (i in user_flags) { | |
if (user_flags[i][0] === '-') { | |
continue; | |
} | |
if (default_flags.indexOf(user_flags[i]) >= 0) { | |
continue; | |
} | |
flags |= ClientConstants[user_flags[i]] || 0x0; | |
} | |
return flags; | |
} | |
static getDefaultFlags(options) { | |
const defaultFlags = [ | |
'LONG_PASSWORD', | |
'FOUND_ROWS', | |
'LONG_FLAG', | |
'CONNECT_WITH_DB', | |
'ODBC', | |
'LOCAL_FILES', | |
'IGNORE_SPACE', | |
'PROTOCOL_41', | |
'IGNORE_SIGPIPE', | |
'TRANSACTIONS', | |
'RESERVED', | |
'SECURE_CONNECTION', | |
'MULTI_RESULTS', | |
'TRANSACTIONS', | |
'SESSION_TRACK', | |
'CONNECT_ATTRS' | |
]; | |
if (options && options.multipleStatements) { | |
defaultFlags.push('MULTI_STATEMENTS'); | |
} | |
defaultFlags.push('PLUGIN_AUTH'); | |
defaultFlags.push('PLUGIN_AUTH_LENENC_CLIENT_DATA'); | |
return defaultFlags; | |
} | |
static getCharsetNumber(charset) { | |
const num = Charsets[charset.toUpperCase()]; | |
if (num === undefined) { | |
throw new TypeError(`Unknown charset '${charset}'`); | |
} | |
return num; | |
} | |
static getSSLProfile(name) { | |
if (!SSLProfiles) { | |
SSLProfiles = require('./constants/ssl_profiles.js'); | |
} | |
const ssl = SSLProfiles[name]; | |
if (ssl === undefined) { | |
throw new TypeError(`Unknown SSL profile '${name}'`); | |
} | |
return ssl; | |
} | |
static parseUrl(url) { | |
const parsedUrl = new URL(url); | |
const options = { | |
host: decodeURIComponent(parsedUrl.hostname), | |
port: parseInt(parsedUrl.port, 10), | |
database: decodeURIComponent(parsedUrl.pathname.slice(1)), | |
user: decodeURIComponent(parsedUrl.username), | |
password: decodeURIComponent(parsedUrl.password), | |
}; | |
parsedUrl.searchParams.forEach((value, key) => { | |
try { | |
// Try to parse this as a JSON expression first | |
options[key] = JSON.parse(value); | |
} catch (err) { | |
// Otherwise assume it is a plain string | |
options[key] = value; | |
} | |
}); | |
return options; | |
} | |
} | |
module.exports = ConnectionConfig; | |