connectionstring.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. 'use strict'
  2. const URL = require('url').URL
  3. const IGNORE_KEYS = ['stream']
  4. const oror = function () {
  5. for (let i = 0, l = arguments.length; i < l; i++) {
  6. if (arguments[i] !== null && arguments[i] !== undefined) {
  7. return arguments[i]
  8. }
  9. }
  10. }
  11. const parseConnectionURI = function (uri) {
  12. const parsed = new URL(uri)
  13. let instance
  14. let user
  15. let password
  16. const path = parsed.pathname.substr(1).split('/')
  17. if (path.length > 1) {
  18. instance = path.shift()
  19. }
  20. if (parsed.username) {
  21. const auth = [parsed.username, parsed.password]
  22. user = decodeURIComponent(auth.shift())
  23. password = decodeURIComponent(auth.join(':'))
  24. }
  25. const port = parsed.port ? `,${parsed.port}` : (instance ? `\\${instance}` : '')
  26. const object = {
  27. server: `${parsed.hostname}${port}`,
  28. uid: user || '',
  29. pwd: password || '',
  30. database: path[0]
  31. }
  32. if (parsed.searchParams) {
  33. parsed.searchParams.forEach((value, key) => {
  34. if (key === 'domain') {
  35. object.uid = `${value}\\${object.uid}`
  36. } else {
  37. object[key] = value
  38. }
  39. })
  40. }
  41. Object.defineProperty(object, 'toString', {
  42. value () {
  43. const out = []
  44. for (const key in this) {
  45. if (IGNORE_KEYS.indexOf(key) === -1) {
  46. out.push(`${key}={${this[key]}}`)
  47. }
  48. }
  49. return out.join(';')
  50. }
  51. })
  52. return object
  53. }
  54. const parseConnectionString = function (string) {
  55. let cursor = 0
  56. let parsing = 'name'
  57. let param = null
  58. let buffer = ''
  59. let quotes = null
  60. const parsed = {}
  61. const original = {}
  62. Object.defineProperty(parsed, '__original__', { value: original })
  63. Object.defineProperty(parsed, 'toString', {
  64. value () {
  65. const out = []
  66. for (const key in this) {
  67. if (IGNORE_KEYS.indexOf(key) === -1) {
  68. const esc = original[key].escape || ['', '']
  69. out.push(`${original[key].name}=${esc[0] || ''}${this[key]}${esc[1] || ''}`)
  70. }
  71. }
  72. return out.join(';')
  73. }
  74. })
  75. while (cursor < string.length) {
  76. const char = string.charAt(cursor)
  77. switch (char) {
  78. case '=':
  79. if (parsing === 'name') {
  80. buffer = buffer.trim()
  81. param = buffer.toLowerCase()
  82. original[param] = { name: buffer }
  83. parsing = 'value'
  84. buffer = ''
  85. } else {
  86. buffer += char
  87. }
  88. break
  89. case '\'': case '"':
  90. if (parsing === 'value') {
  91. if (!buffer.trim().length) {
  92. // value is wrapped in qotes
  93. original[param].escape = [char, char]
  94. quotes = char
  95. buffer = ''
  96. } else if (quotes) {
  97. if (char === quotes) {
  98. // found same char as used for wrapping quotes
  99. if (char === string.charAt(cursor + 1)) {
  100. // escaped quote
  101. buffer += char
  102. cursor++
  103. } else {
  104. // end of value
  105. parsed[param] = buffer
  106. param = null
  107. parsing = null
  108. buffer = ''
  109. quotes = null
  110. }
  111. } else {
  112. buffer += char
  113. }
  114. } else {
  115. buffer += char
  116. }
  117. } else {
  118. throw new Error('Invalid connection string.')
  119. }
  120. break
  121. case '{':
  122. if (parsing === 'value') {
  123. if (buffer.trim().length === 0) {
  124. // value is wrapped in qotes
  125. original[param].escape = ['{', '}']
  126. quotes = '{}'
  127. buffer = ''
  128. } else {
  129. buffer += char
  130. }
  131. } else {
  132. throw new Error('Invalid connection string.')
  133. }
  134. break
  135. case '}':
  136. if (parsing === 'value') {
  137. if (quotes === '{}') {
  138. // end of value
  139. parsed[param] = buffer
  140. param = null
  141. parsing = null
  142. buffer = ''
  143. quotes = null
  144. } else {
  145. buffer += char
  146. }
  147. } else {
  148. throw new Error('Invalid connection string.')
  149. }
  150. break
  151. case ';':
  152. if (parsing === 'value') {
  153. if (quotes) {
  154. buffer += char
  155. } else {
  156. // end of value
  157. parsed[param] = buffer
  158. param = null
  159. parsing = 'name'
  160. buffer = ''
  161. }
  162. } else {
  163. buffer = ''
  164. parsing = 'name'
  165. }
  166. break
  167. default:
  168. buffer += char
  169. }
  170. cursor++
  171. }
  172. if (parsing === 'value') {
  173. // end of value
  174. parsed[param] = buffer
  175. }
  176. return parsed
  177. }
  178. const resolveConnectionString = function (string, driver) {
  179. const parsed = /^(mssql|tedious|msnodesql|tds):\/\//i.test(string) ? parseConnectionURI(string) : parseConnectionString(string)
  180. const stream = (parsed.stream || '').toLowerCase()
  181. const encrypt = (parsed.encrypt || '').toLowerCase()
  182. if (driver === 'msnodesqlv8') {
  183. parsed.driver = 'SQL Server Native Client 11.0'
  184. if (parsed.__original__) {
  185. parsed.__original__.driver = { name: 'Driver', escape: ['{', '}'] }
  186. }
  187. return { connectionString: parsed.toString() }
  188. }
  189. let user = parsed.uid || parsed.uid || parsed['user id']
  190. let server = parsed.server || parsed.address || parsed.addr || parsed['data source'] || parsed['network address']
  191. const config = {
  192. password: oror(parsed.pwd, parsed.password),
  193. database: oror(parsed.database, parsed['initial catalog']),
  194. connectionTimeout: oror(parsed.connectionTimeout, parsed.timeout, parsed['connect timeout'], parsed['connection timeout']),
  195. requestTimeout: oror(parsed.requestTimeout, parsed['request timeout']),
  196. stream: stream === 'true' || stream === 'yes' || stream === '1',
  197. options: {
  198. readOnlyIntent: parsed.applicationintent && parsed.applicationintent.toLowerCase() === 'readonly',
  199. encrypt: encrypt === 'true' || encrypt === 'yes' || encrypt === '1'
  200. }
  201. }
  202. if (parsed.useUTC != null) {
  203. const utc = parsed.useUTC.toLowerCase()
  204. config.options.useUTC = utc === 'true' || utc === 'yes' || utc === '1'
  205. }
  206. if (config.connectionTimeout != null) {
  207. config.connectionTimeout = parseInt(config.connectionTimeout, 10) * 1000
  208. }
  209. if (config.requestTimeout != null) {
  210. config.requestTimeout = parseInt(config.requestTimeout, 10)
  211. }
  212. if (parsed.multisubnetfailover != null) {
  213. config.options.multiSubnetFailover = parsed.multisubnetfailover.toLowerCase() === 'true'
  214. }
  215. if (/^(.*)\\(.*)$/.exec(user)) {
  216. config.domain = RegExp.$1
  217. user = RegExp.$2
  218. }
  219. if (server) {
  220. server = server.trim()
  221. if (/^np:/i.test(server)) {
  222. throw new Error('Connection via Named Pipes is not supported.')
  223. }
  224. if (/^tcp:/i.test(server)) {
  225. server = server.substr(4)
  226. }
  227. if (/^(.*)\\(.*)$/.exec(server)) {
  228. server = RegExp.$1
  229. config.options.instanceName = RegExp.$2
  230. }
  231. if (/^(.*),(.*)$/.exec(server)) {
  232. server = RegExp.$1.trim()
  233. config.port = parseInt(RegExp.$2.trim(), 10)
  234. }
  235. if (server === '.' || server === '(.)' || server.toLowerCase() === '(localdb)' || server.toLowerCase() === '(local)') {
  236. server = 'localhost'
  237. }
  238. }
  239. config.user = user
  240. config.server = server
  241. return config
  242. }
  243. module.exports = {
  244. parse: parseConnectionString,
  245. resolve: resolveConnectionString
  246. }