connection_config.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. // This file was modified by Oracle on September 21, 2021.
  2. // New connection options for additional authentication factors were
  3. // introduced.
  4. // Multi-factor authentication capability is now enabled if one of these
  5. // options is used.
  6. // Modifications copyright (c) 2021, Oracle and/or its affiliates.
  7. 'use strict';
  8. const { URL } = require('url');
  9. const ClientConstants = require('./constants/client');
  10. const Charsets = require('./constants/charsets');
  11. const { version } = require('../package.json');
  12. let SSLProfiles = null;
  13. const validOptions = {
  14. authPlugins: 1,
  15. authSwitchHandler: 1,
  16. bigNumberStrings: 1,
  17. charset: 1,
  18. charsetNumber: 1,
  19. compress: 1,
  20. connectAttributes: 1,
  21. connectTimeout: 1,
  22. database: 1,
  23. dateStrings: 1,
  24. debug: 1,
  25. decimalNumbers: 1,
  26. enableKeepAlive: 1,
  27. flags: 1,
  28. host: 1,
  29. insecureAuth: 1,
  30. infileStreamFactory: 1,
  31. isServer: 1,
  32. keepAliveInitialDelay: 1,
  33. localAddress: 1,
  34. maxPreparedStatements: 1,
  35. multipleStatements: 1,
  36. namedPlaceholders: 1,
  37. nestTables: 1,
  38. password: 1,
  39. // with multi-factor authentication, the main password (used for the first
  40. // authentication factor) can be provided via password1
  41. password1: 1,
  42. password2: 1,
  43. password3: 1,
  44. passwordSha1: 1,
  45. pool: 1,
  46. port: 1,
  47. queryFormat: 1,
  48. rowsAsArray: 1,
  49. socketPath: 1,
  50. ssl: 1,
  51. stream: 1,
  52. stringifyObjects: 1,
  53. supportBigNumbers: 1,
  54. timezone: 1,
  55. trace: 1,
  56. typeCast: 1,
  57. uri: 1,
  58. user: 1,
  59. disableEval: 1,
  60. // These options are used for Pool
  61. connectionLimit: 1,
  62. maxIdle: 1,
  63. idleTimeout: 1,
  64. Promise: 1,
  65. queueLimit: 1,
  66. waitForConnections: 1,
  67. jsonStrings: 1,
  68. gracefulEnd: 1,
  69. };
  70. class ConnectionConfig {
  71. constructor(options) {
  72. if (typeof options === 'string') {
  73. options = ConnectionConfig.parseUrl(options);
  74. } else if (options && options.uri) {
  75. const uriOptions = ConnectionConfig.parseUrl(options.uri);
  76. for (const key in uriOptions) {
  77. if (!Object.prototype.hasOwnProperty.call(uriOptions, key)) continue;
  78. if (options[key]) continue;
  79. options[key] = uriOptions[key];
  80. }
  81. }
  82. for (const key in options) {
  83. if (!Object.prototype.hasOwnProperty.call(options, key)) continue;
  84. if (validOptions[key] !== 1) {
  85. // REVIEW: Should this be emitted somehow?
  86. // eslint-disable-next-line no-console
  87. console.error(
  88. `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`
  89. );
  90. }
  91. }
  92. this.isServer = options.isServer;
  93. this.stream = options.stream;
  94. this.host = options.host || 'localhost';
  95. this.port =
  96. (typeof options.port === 'string'
  97. ? parseInt(options.port, 10)
  98. : options.port) || 3306;
  99. this.localAddress = options.localAddress;
  100. this.socketPath = options.socketPath;
  101. this.user = options.user || undefined;
  102. // for the purpose of multi-factor authentication, or not, the main
  103. // password (used for the 1st authentication factor) can also be
  104. // provided via the "password1" option
  105. this.password = options.password || options.password1 || undefined;
  106. this.password2 = options.password2 || undefined;
  107. this.password3 = options.password3 || undefined;
  108. this.passwordSha1 = options.passwordSha1 || undefined;
  109. this.database = options.database;
  110. this.connectTimeout = isNaN(options.connectTimeout)
  111. ? 10 * 1000
  112. : options.connectTimeout;
  113. this.insecureAuth = options.insecureAuth || false;
  114. this.infileStreamFactory = options.infileStreamFactory || undefined;
  115. this.supportBigNumbers = options.supportBigNumbers || false;
  116. this.bigNumberStrings = options.bigNumberStrings || false;
  117. this.decimalNumbers = options.decimalNumbers || false;
  118. this.dateStrings = options.dateStrings || false;
  119. this.debug = options.debug;
  120. this.trace = options.trace !== false;
  121. this.stringifyObjects = options.stringifyObjects || false;
  122. this.enableKeepAlive = options.enableKeepAlive !== false;
  123. this.keepAliveInitialDelay = options.keepAliveInitialDelay;
  124. if (
  125. options.timezone &&
  126. !/^(?:local|Z|[ +-]\d\d:\d\d)$/.test(options.timezone)
  127. ) {
  128. // strictly supports timezones specified by mysqljs/mysql:
  129. // https://github.com/mysqljs/mysql#user-content-connection-options
  130. // eslint-disable-next-line no-console
  131. console.error(
  132. `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`
  133. );
  134. // SqlStrings falls back to UTC on invalid timezone
  135. this.timezone = 'Z';
  136. } else {
  137. this.timezone = options.timezone || 'local';
  138. }
  139. this.queryFormat = options.queryFormat;
  140. this.pool = options.pool || undefined;
  141. this.ssl =
  142. typeof options.ssl === 'string'
  143. ? ConnectionConfig.getSSLProfile(options.ssl)
  144. : options.ssl || false;
  145. this.multipleStatements = options.multipleStatements || false;
  146. this.rowsAsArray = options.rowsAsArray || false;
  147. this.namedPlaceholders = options.namedPlaceholders || false;
  148. this.nestTables =
  149. options.nestTables === undefined ? undefined : options.nestTables;
  150. this.typeCast = options.typeCast === undefined ? true : options.typeCast;
  151. this.disableEval = Boolean(options.disableEval);
  152. if (this.timezone[0] === ' ') {
  153. // "+" is a url encoded char for space so it
  154. // gets translated to space when giving a
  155. // connection string..
  156. this.timezone = `+${this.timezone.slice(1)}`;
  157. }
  158. if (this.ssl) {
  159. if (typeof this.ssl !== 'object') {
  160. throw new TypeError(
  161. `SSL profile must be an object, instead it's a ${typeof this.ssl}`
  162. );
  163. }
  164. // Default rejectUnauthorized to true
  165. this.ssl.rejectUnauthorized = this.ssl.rejectUnauthorized !== false;
  166. }
  167. this.maxPacketSize = 0;
  168. this.charsetNumber = options.charset
  169. ? ConnectionConfig.getCharsetNumber(options.charset)
  170. : options.charsetNumber || Charsets.UTF8MB4_UNICODE_CI;
  171. this.compress = options.compress || false;
  172. this.authPlugins = options.authPlugins;
  173. this.authSwitchHandler = options.authSwitchHandler;
  174. this.clientFlags = ConnectionConfig.mergeFlags(
  175. ConnectionConfig.getDefaultFlags(options),
  176. options.flags || ''
  177. );
  178. // Default connection attributes
  179. // https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html
  180. const defaultConnectAttributes = {
  181. _client_name: 'Node-MySQL-2',
  182. _client_version: version,
  183. };
  184. this.connectAttributes = {
  185. ...defaultConnectAttributes,
  186. ...(options.connectAttributes || {}),
  187. };
  188. this.maxPreparedStatements = options.maxPreparedStatements || 16000;
  189. this.jsonStrings = options.jsonStrings || false;
  190. this.gracefulEnd = options.gracefulEnd || false;
  191. }
  192. static mergeFlags(default_flags, user_flags) {
  193. let flags = 0x0,
  194. i;
  195. if (!Array.isArray(user_flags)) {
  196. user_flags = String(user_flags || '')
  197. .toUpperCase()
  198. .split(/\s*,+\s*/);
  199. }
  200. // add default flags unless "blacklisted"
  201. for (i in default_flags) {
  202. if (user_flags.indexOf(`-${default_flags[i]}`) >= 0) {
  203. continue;
  204. }
  205. flags |= ClientConstants[default_flags[i]] || 0x0;
  206. }
  207. // add user flags unless already already added
  208. for (i in user_flags) {
  209. if (user_flags[i][0] === '-') {
  210. continue;
  211. }
  212. if (default_flags.indexOf(user_flags[i]) >= 0) {
  213. continue;
  214. }
  215. flags |= ClientConstants[user_flags[i]] || 0x0;
  216. }
  217. return flags;
  218. }
  219. static getDefaultFlags(options) {
  220. const defaultFlags = [
  221. 'LONG_PASSWORD',
  222. 'FOUND_ROWS',
  223. 'LONG_FLAG',
  224. 'CONNECT_WITH_DB',
  225. 'ODBC',
  226. 'LOCAL_FILES',
  227. 'IGNORE_SPACE',
  228. 'PROTOCOL_41',
  229. 'IGNORE_SIGPIPE',
  230. 'TRANSACTIONS',
  231. 'RESERVED',
  232. 'SECURE_CONNECTION',
  233. 'MULTI_RESULTS',
  234. 'TRANSACTIONS',
  235. 'SESSION_TRACK',
  236. 'CONNECT_ATTRS',
  237. ];
  238. if (options && options.multipleStatements) {
  239. defaultFlags.push('MULTI_STATEMENTS');
  240. }
  241. defaultFlags.push('PLUGIN_AUTH');
  242. defaultFlags.push('PLUGIN_AUTH_LENENC_CLIENT_DATA');
  243. return defaultFlags;
  244. }
  245. static getCharsetNumber(charset) {
  246. const num = Charsets[charset.toUpperCase()];
  247. if (num === undefined) {
  248. throw new TypeError(`Unknown charset '${charset}'`);
  249. }
  250. return num;
  251. }
  252. static getSSLProfile(name) {
  253. if (!SSLProfiles) {
  254. SSLProfiles = require('./constants/ssl_profiles.js');
  255. }
  256. const ssl = SSLProfiles[name];
  257. if (ssl === undefined) {
  258. throw new TypeError(`Unknown SSL profile '${name}'`);
  259. }
  260. return ssl;
  261. }
  262. static parseUrl(url) {
  263. const parsedUrl = new URL(url);
  264. const options = {
  265. host: decodeURIComponent(parsedUrl.hostname),
  266. port: parseInt(parsedUrl.port, 10),
  267. database: decodeURIComponent(parsedUrl.pathname.slice(1)),
  268. user: decodeURIComponent(parsedUrl.username),
  269. password: decodeURIComponent(parsedUrl.password),
  270. };
  271. parsedUrl.searchParams.forEach((value, key) => {
  272. try {
  273. // Try to parse this as a JSON expression first
  274. options[key] = JSON.parse(value);
  275. } catch (err) {
  276. // Otherwise assume it is a plain string
  277. options[key] = value;
  278. }
  279. });
  280. return options;
  281. }
  282. }
  283. module.exports = ConnectionConfig;