connection_config.js 9.3 KB

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