connection_config.js 9.2 KB

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