connection-options.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. 'use strict';
  2. const Collations = require('../const/collations.js');
  3. const urlFormat = /mariadb:\/\/(([^/@:]+)?(:([^/]+))?@)?(([^/:]+)(:([0-9]+))?)\/([^?]+)(\?(.*))?$/;
  4. const moment = require('moment-timezone');
  5. const Errors = require('../misc/errors');
  6. /**
  7. * Default option similar to mysql driver.
  8. * known differences
  9. * - no queryFormat option. Permitting client to parse is a security risk. Best is to give SQL + parameters
  10. * Only possible Objects are :
  11. * - Buffer
  12. * - Date
  13. * - Object that implement toSqlString function
  14. * - JSON object
  15. * + rowsAsArray (in mysql2) permit to have rows by index, not by name. Avoiding to parsing metadata string => faster
  16. */
  17. class ConnectionOptions {
  18. constructor(opts) {
  19. if (typeof opts === 'string') {
  20. opts = ConnectionOptions.parse(opts);
  21. }
  22. if (!opts) opts = {};
  23. this.bigNumberStrings = opts.bigNumberStrings || false;
  24. this.bulk = opts.bulk === undefined || opts.bulk;
  25. if (opts.charset && typeof opts.charset === 'string') {
  26. this.collation = Collations.fromCharset(opts.charset.toLowerCase());
  27. if (this.collation === undefined) {
  28. this.collation = Collations.fromName(opts.charset.toUpperCase());
  29. if (this.collation !== undefined) {
  30. console.log(
  31. "warning: please use option 'collation' " +
  32. "in replacement of 'charset' when using a collation name ('" +
  33. opts.charset +
  34. "')\n" +
  35. "(collation looks like 'UTF8MB4_UNICODE_CI', charset like 'utf8')."
  36. );
  37. }
  38. }
  39. if (this.collation === undefined)
  40. throw new RangeError("Unknown charset '" + opts.charset + "'");
  41. } else if (opts.collation && typeof opts.collation === 'string') {
  42. this.collation = Collations.fromName(opts.collation.toUpperCase());
  43. if (this.collation === undefined)
  44. throw new RangeError("Unknown collation '" + opts.collation + "'");
  45. } else {
  46. this.collation = Collations.fromIndex(opts.charsetNumber) || Collations.fromIndex(224); //UTF8MB4_UNICODE_CI;
  47. }
  48. this.compress = opts.compress || false;
  49. this.logPackets = opts.logPackets || false;
  50. this.connectAttributes = opts.connectAttributes || false;
  51. this.connectTimeout = opts.connectTimeout === undefined ? 10000 : opts.connectTimeout;
  52. this.queryTimeout = opts.queryTimeout === undefined ? 0 : opts.queryTimeout;
  53. this.socketTimeout = opts.socketTimeout === undefined ? 0 : opts.socketTimeout;
  54. this.database = opts.database;
  55. this.checkDuplicate = opts.checkDuplicate === undefined ? true : opts.checkDuplicate;
  56. this.dateStrings = opts.dateStrings || false;
  57. this.debug = opts.debug || false;
  58. this.debugCompress = opts.debugCompress || false;
  59. this.debugLen = opts.debugLen || 256;
  60. this.foundRows = opts.foundRows === undefined || opts.foundRows;
  61. this.host = opts.host || 'localhost';
  62. this.rsaPublicKey = opts.rsaPublicKey;
  63. this.cachingRsaPublicKey = opts.cachingRsaPublicKey;
  64. this.allowPublicKeyRetrieval = opts.allowPublicKeyRetrieval || false;
  65. this.initSql = opts.initSql;
  66. this.forceVersionCheck = opts.forceVersionCheck || false;
  67. this.maxAllowedPacket = opts.maxAllowedPacket;
  68. this.metaAsArray = opts.metaAsArray || false;
  69. this.multipleStatements = opts.multipleStatements || false;
  70. this.namedPlaceholders = opts.namedPlaceholders || false;
  71. this.nestTables = opts.nestTables;
  72. this.password = opts.password;
  73. this.autoJsonMap = opts.autoJsonMap === undefined ? true : opts.autoJsonMap;
  74. this.arrayParenthesis = opts.arrayParenthesis || false;
  75. this.keepAliveDelay = opts.keepAliveDelay === undefined ? 0 : opts.keepAliveDelay;
  76. this.permitSetMultiParamEntries = opts.permitSetMultiParamEntries || false;
  77. this.permitConnectionWhenExpired = opts.permitConnectionWhenExpired || false;
  78. this.pipelining = opts.pipelining;
  79. if (opts.pipelining === undefined) {
  80. this.permitLocalInfile = opts.permitLocalInfile || false;
  81. this.pipelining = !this.permitLocalInfile;
  82. } else {
  83. this.pipelining = opts.pipelining;
  84. if (opts.permitLocalInfile === true && this.pipelining) {
  85. throw new Error(
  86. 'enabling options `permitLocalInfile` and ' +
  87. '`pipelining` is not possible, options are incompatible.'
  88. );
  89. }
  90. this.permitLocalInfile = this.pipelining ? false : opts.permitLocalInfile || false;
  91. }
  92. this.port = opts.port || 3306;
  93. this.rowsAsArray = opts.rowsAsArray || false;
  94. this.socketPath = opts.socketPath;
  95. this.sessionVariables = opts.sessionVariables;
  96. this.ssl = opts.ssl;
  97. if (opts.ssl) {
  98. if (typeof opts.ssl !== 'boolean' && typeof opts.ssl !== 'string') {
  99. this.ssl.rejectUnauthorized = opts.ssl.rejectUnauthorized !== false;
  100. }
  101. }
  102. this.supportBigNumbers = opts.supportBigNumbers || false;
  103. this.supportBigInt = opts.supportBigInt || false;
  104. this.timezone = opts.timezone || 'local';
  105. this.skipSetTimezone = opts.skipSetTimezone || false;
  106. if (this.timezone && this.timezone !== 'local' && this.timezone !== 'auto') {
  107. let tzName = this.timezone;
  108. if (this.timezone === 'Z') {
  109. tzName = 'Etc/UTC';
  110. } else {
  111. const matched = this.timezone.match(/([+\-\s])(\d\d):?(\d\d)?/);
  112. if (matched) {
  113. const hour = (matched[1] === '-' ? 1 : -1) * Number.parseInt(matched[2], 10);
  114. const minutes = matched.length > 2 && matched[3] ? Number.parseInt(matched[3], 10) : 0;
  115. if (minutes > 0) {
  116. throw new RangeError(
  117. "timezone format incompatible with IANA standard timezone format was '" +
  118. this.timezone +
  119. "'"
  120. );
  121. }
  122. if (hour == 0) {
  123. tzName = 'Etc/UTC';
  124. } else {
  125. tzName = 'Etc/GMT' + (matched[1] === '-' ? '+' : '') + hour;
  126. }
  127. }
  128. }
  129. this.localTz = moment.tz.guess();
  130. if (tzName === this.localTz) {
  131. this.tz = null;
  132. } else {
  133. this.tz = tzName;
  134. if (!moment.tz.zone(tzName)) {
  135. throw Errors.createError(
  136. "Unknown IANA timezone '" + tzName + "'.",
  137. true,
  138. null,
  139. '08S01',
  140. Errors.ER_WRONG_IANA_TIMEZONE
  141. );
  142. }
  143. }
  144. }
  145. this.trace = opts.trace || false;
  146. this.typeCast = opts.typeCast;
  147. if (this.typeCast !== undefined && typeof this.typeCast !== 'function') {
  148. this.typeCast = undefined;
  149. }
  150. this.user = opts.user || process.env.USERNAME;
  151. if (this.maxAllowedPacket && !Number.isInteger(this.maxAllowedPacket)) {
  152. throw new RangeError(
  153. "maxAllowedPacket must be an integer. was '" + this.maxAllowedPacket + "'"
  154. );
  155. }
  156. }
  157. /**
  158. * When parsing from String, correcting type.
  159. *
  160. * @param opts options
  161. * @return {opts}
  162. */
  163. static parseOptionDataType(opts) {
  164. if (opts.bigNumberStrings) opts.bigNumberStrings = opts.bigNumberStrings == 'true';
  165. if (opts.bulk) opts.bulk = opts.bulk == 'true';
  166. if (opts.rsaPublicKey) opts.rsaPublicKey = opts.rsaPublicKey;
  167. if (opts.cachingRsaPublicKey) opts.cachingRsaPublicKey = opts.cachingRsaPublicKey;
  168. if (opts.logPackets) opts.logPackets = opts.logPackets == 'true';
  169. if (opts.allowPublicKeyRetrieval)
  170. opts.allowPublicKeyRetrieval = opts.allowPublicKeyRetrieval == 'true';
  171. if (opts.charsetNumber && !isNaN(Number.parseInt(opts.charsetNumber))) {
  172. opts.charsetNumber = Number.parseInt(opts.charsetNumber);
  173. }
  174. if (opts.compress) opts.compress = opts.compress == 'true';
  175. if (opts.connectAttributes) opts.connectAttributes = JSON.parse(opts.connectAttributes);
  176. if (opts.connectTimeout) opts.connectTimeout = parseInt(opts.connectTimeout);
  177. if (opts.keepAliveDelay) opts.keepAliveDelay = parseInt(opts.keepAliveDelay);
  178. if (opts.socketTimeout) opts.socketTimeout = parseInt(opts.socketTimeout);
  179. if (opts.dateStrings) opts.dateStrings = opts.dateStrings == 'true';
  180. if (opts.debug) opts.debug = opts.debug == 'true';
  181. if (opts.autoJsonMap) opts.autoJsonMap = opts.autoJsonMap == 'true';
  182. if (opts.arrayParenthesis) opts.arrayParenthesis = opts.arrayParenthesis == 'true';
  183. if (opts.skipSetTimezone) opts.skipSetTimezone = opts.skipSetTimezone == 'true';
  184. if (opts.checkDuplicate) opts.checkDuplicate = opts.checkDuplicate == 'true';
  185. if (opts.debugCompress) opts.debugCompress = opts.debugCompress == 'true';
  186. if (opts.debugLen) opts.debugLen = parseInt(opts.debugLen);
  187. if (opts.queryTimeout) opts.queryTimeout = parseInt(opts.queryTimeout);
  188. if (opts.foundRows) opts.foundRows = opts.foundRows == 'true';
  189. if (opts.maxAllowedPacket && !isNaN(Number.parseInt(opts.maxAllowedPacket)))
  190. opts.maxAllowedPacket = parseInt(opts.maxAllowedPacket);
  191. if (opts.metaAsArray) opts.metaAsArray = opts.metaAsArray == 'true';
  192. if (opts.multipleStatements) opts.multipleStatements = opts.multipleStatements == 'true';
  193. if (opts.namedPlaceholders) opts.namedPlaceholders = opts.namedPlaceholders == 'true';
  194. if (opts.nestTables) opts.nestTables = opts.nestTables == 'true';
  195. if (opts.permitSetMultiParamEntries)
  196. opts.permitSetMultiParamEntries = opts.permitSetMultiParamEntries == 'true';
  197. if (opts.pipelining) opts.pipelining = opts.pipelining == 'true';
  198. if (opts.forceVersionCheck) opts.forceVersionCheck = opts.forceVersionCheck == 'true';
  199. if (opts.rowsAsArray) opts.rowsAsArray = opts.rowsAsArray == 'true';
  200. if (opts.supportBigNumbers) opts.supportBigNumbers = opts.supportBigNumbers == 'true';
  201. if (opts.supportBigInt) opts.supportBigInt = opts.supportBigInt == 'true';
  202. if (opts.trace) opts.trace = opts.trace == 'true';
  203. if (opts.ssl && (opts.ssl == 'true' || opts.ssl == 'false')) opts.ssl = opts.ssl == 'true';
  204. return opts;
  205. }
  206. static parse(opts) {
  207. const matchResults = opts.match(urlFormat);
  208. if (!matchResults) {
  209. throw new Error(
  210. "error parsing connection string '" +
  211. opts +
  212. "'. format must be 'mariadb://[<user>[:<password>]@]<host>[:<port>]/[<db>[?<opt1>=<value1>[&<opt2>=<value2>]]]'"
  213. );
  214. }
  215. const options = {
  216. user: matchResults[2] ? decodeURIComponent(matchResults[2]) : undefined,
  217. password: matchResults[4] ? decodeURIComponent(matchResults[4]) : undefined,
  218. host: matchResults[6] ? decodeURIComponent(matchResults[6]) : matchResults[6],
  219. port: matchResults[8] ? parseInt(matchResults[8]) : undefined,
  220. database: matchResults[9] ? decodeURIComponent(matchResults[9]) : matchResults[9]
  221. };
  222. const variousOptsString = matchResults[11];
  223. if (variousOptsString) {
  224. const keyVals = variousOptsString.split('&');
  225. keyVals.forEach(function (keyVal) {
  226. const equalIdx = keyVal.indexOf('=');
  227. if (equalIdx !== 1) {
  228. let val = keyVal.substring(equalIdx + 1);
  229. val = val ? decodeURIComponent(val) : undefined;
  230. options[keyVal.substring(0, equalIdx)] = val;
  231. }
  232. });
  233. }
  234. return this.parseOptionDataType(options);
  235. }
  236. }
  237. module.exports = ConnectionOptions;