293 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // 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.
 | |
| 
 | |
| 'use strict';
 | |
| 
 | |
| 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,
 | |
|   disableEval: 1,
 | |
|   // These options are used for Pool
 | |
|   connectionLimit: 1,
 | |
|   maxIdle: 1,
 | |
|   idleTimeout: 1,
 | |
|   Promise: 1,
 | |
|   queueLimit: 1,
 | |
|   waitForConnections: 1,
 | |
|   jsonStrings: 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;
 | |
|     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;
 | |
|     this.disableEval = Boolean(options.disableEval);
 | |
|     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;
 | |
|     this.jsonStrings = options.jsonStrings || false;
 | |
|   }
 | |
| 
 | |
|   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;
 |