parse.js 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. const Errors = require('../misc/errors');
  2. const State = {
  3. Normal: 1 /* inside query */,
  4. String: 2 /* inside string */,
  5. SlashStarComment: 3 /* inside slash-star comment */,
  6. Escape: 4 /* found backslash */,
  7. EOLComment: 5 /* # comment, or // comment, or -- comment */,
  8. Backtick: 6 /* found backtick */,
  9. Placeholder: 7 /* found placeholder */
  10. };
  11. /**
  12. * Split query according to parameters (question mark).
  13. * Question mark in comment are not taken in account
  14. *
  15. * @returns {Array} query separated by parameters
  16. */
  17. module.exports.splitQuery = function (sql) {
  18. let partList = [];
  19. let state = State.Normal;
  20. let lastChar = '\0';
  21. let singleQuotes = false;
  22. let lastParameterPosition = 0;
  23. let idx = 0;
  24. let car = sql.charAt(idx++);
  25. while (car !== '') {
  26. if (
  27. state === State.Escape &&
  28. !((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
  29. ) {
  30. state = State.String;
  31. car = sql.charAt(idx++);
  32. continue;
  33. }
  34. switch (car) {
  35. case '*':
  36. if (state === State.Normal && lastChar == '/') state = State.SlashStarComment;
  37. break;
  38. case '/':
  39. if (state === State.SlashStarComment && lastChar == '*') state = State.Normal;
  40. break;
  41. case '#':
  42. if (state === State.Normal) state = State.EOLComment;
  43. break;
  44. case '-':
  45. if (state === State.Normal && lastChar == '-') {
  46. state = State.EOLComment;
  47. }
  48. break;
  49. case '\n':
  50. if (state === State.EOLComment) {
  51. state = State.Normal;
  52. }
  53. break;
  54. case '"':
  55. if (state === State.Normal) {
  56. state = State.String;
  57. singleQuotes = false;
  58. } else if (state === State.String && !singleQuotes) {
  59. state = State.Normal;
  60. } else if (state === State.Escape && !singleQuotes) {
  61. state = State.String;
  62. }
  63. break;
  64. case "'":
  65. if (state === State.Normal) {
  66. state = State.String;
  67. singleQuotes = true;
  68. } else if (state === State.String && singleQuotes) {
  69. state = State.Normal;
  70. } else if (state === State.Escape && singleQuotes) {
  71. state = State.String;
  72. }
  73. break;
  74. case '\\':
  75. if (state === State.String) state = State.Escape;
  76. break;
  77. case '?':
  78. if (state === State.Normal) {
  79. partList.push(sql.substring(lastParameterPosition, idx - 1));
  80. lastParameterPosition = idx;
  81. }
  82. break;
  83. case '`':
  84. if (state === State.Backtick) {
  85. state = State.Normal;
  86. } else if (state === State.Normal) {
  87. state = State.Backtick;
  88. }
  89. break;
  90. }
  91. lastChar = car;
  92. car = sql.charAt(idx++);
  93. }
  94. if (lastParameterPosition === 0) {
  95. partList.push(sql);
  96. } else {
  97. partList.push(sql.substring(lastParameterPosition));
  98. }
  99. return partList;
  100. };
  101. /**
  102. * Split query according to parameters using placeholder.
  103. *
  104. * @param sql sql with placeholders
  105. * @param info connection information
  106. * @param initialValues placeholder object
  107. * @param displaySql display sql function
  108. * @returns {{parts: Array, values: Array}}
  109. */
  110. module.exports.splitQueryPlaceholder = function (sql, info, initialValues, displaySql) {
  111. let partList = [];
  112. let values = [];
  113. let state = State.Normal;
  114. let lastChar = '\0';
  115. let singleQuotes = false;
  116. let lastParameterPosition = 0;
  117. let idx = 0;
  118. let car = sql.charAt(idx++);
  119. let placeholderName;
  120. while (car !== '') {
  121. if (
  122. state === State.Escape &&
  123. !((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
  124. ) {
  125. state = State.String;
  126. car = sql.charAt(idx++);
  127. continue;
  128. }
  129. switch (car) {
  130. case '*':
  131. if (state === State.Normal && lastChar == '/') state = State.SlashStarComment;
  132. break;
  133. case '/':
  134. if (state === State.SlashStarComment && lastChar == '*') state = State.Normal;
  135. break;
  136. case '#':
  137. if (state === State.Normal) state = State.EOLComment;
  138. break;
  139. case '-':
  140. if (state === State.Normal && lastChar == '-') {
  141. state = State.EOLComment;
  142. }
  143. break;
  144. case '\n':
  145. if (state === State.EOLComment) {
  146. state = State.Normal;
  147. }
  148. break;
  149. case '"':
  150. if (state === State.Normal) {
  151. state = State.String;
  152. singleQuotes = false;
  153. } else if (state === State.String && !singleQuotes) {
  154. state = State.Normal;
  155. } else if (state === State.Escape && !singleQuotes) {
  156. state = State.String;
  157. }
  158. break;
  159. case "'":
  160. if (state === State.Normal) {
  161. state = State.String;
  162. singleQuotes = true;
  163. } else if (state === State.String && singleQuotes) {
  164. state = State.Normal;
  165. singleQuotes = false;
  166. } else if (state === State.Escape && singleQuotes) {
  167. state = State.String;
  168. }
  169. break;
  170. case '\\':
  171. if (state === State.String) state = State.Escape;
  172. break;
  173. case ':':
  174. if (state === State.Normal) {
  175. partList.push(sql.substring(lastParameterPosition, idx - 1));
  176. placeholderName = '';
  177. while (
  178. ((car = sql.charAt(idx++)) !== '' && car >= '0' && car <= '9') ||
  179. (car >= 'A' && car <= 'Z') ||
  180. (car >= 'a' && car <= 'z') ||
  181. car === '-' ||
  182. car === '_'
  183. ) {
  184. placeholderName += car;
  185. }
  186. idx--;
  187. const val = initialValues[placeholderName];
  188. if (val === undefined) {
  189. throw Errors.createError(
  190. "Placeholder '" + placeholderName + "' is not defined\n" + displaySql.call(),
  191. false,
  192. info,
  193. 'HY000',
  194. Errors.ER_PLACEHOLDER_UNDEFINED
  195. );
  196. }
  197. values.push(val);
  198. lastParameterPosition = idx;
  199. }
  200. break;
  201. case '`':
  202. if (state === State.Backtick) {
  203. state = State.Normal;
  204. } else if (state === State.Normal) {
  205. state = State.Backtick;
  206. }
  207. }
  208. lastChar = car;
  209. car = sql.charAt(idx++);
  210. }
  211. if (lastParameterPosition === 0) {
  212. partList.push(sql);
  213. } else {
  214. partList.push(sql.substring(lastParameterPosition));
  215. }
  216. return { parts: partList, values: values };
  217. };
  218. /**
  219. * Split query according to parameters (question mark).
  220. *
  221. * The only rewritten queries follow these notation: INSERT [LOW_PRIORITY | DELAYED |
  222. * HIGH_PRIORITY] [IGNORE] [INTO] tbl_name [PARTITION (partition_list)] [(col,...)] {VALUES |
  223. * VALUE} (...) [ ON DUPLICATE KEY UPDATE col=expr [, col=expr] ... ] With expr without
  224. * parameter.
  225. *
  226. * Query with INSERT ... SELECT / containing LAST_INSERT_ID() will not be rewritten
  227. *
  228. * query parts will be split this way :
  229. * - pre-value part
  230. * - after value part
  231. * [- after parameter part] (after each parameter)
  232. * - ending part
  233. *
  234. * example : INSERT INTO MyTABLE VALUES (9, ?, 5, ?, 8) ON DUPLICATE KEY UPDATE col2=col2+10
  235. * will result in :
  236. * - pre-value : "INSERT INTO MyTABLE VALUES"
  237. * - after value : " (9, "
  238. * - after parameter : ", 5, "
  239. * - after parameter : ", 8)"
  240. * - ending : " ON DUPLICATE KEY UPDATE col2=col2+10"
  241. *
  242. *
  243. * @returns {JSON} query separated by parameters
  244. */
  245. module.exports.splitRewritableQuery = function (sql) {
  246. let reWritablePrepare = true;
  247. let multipleQueriesPrepare = true;
  248. let partList = [];
  249. let lastChar = '\0';
  250. let lastParameterPosition = 0;
  251. let preValuePart1 = null;
  252. let preValuePart2 = null;
  253. let postValuePart = null;
  254. let singleQuotes = false;
  255. let isInParenthesis = 0;
  256. let isFirstChar = true;
  257. let isInsert = false;
  258. let semicolon = false;
  259. let hasParam = false;
  260. let state = State.Normal;
  261. let idx = 0;
  262. let car = sql.charAt(idx++);
  263. while (car !== '') {
  264. if (
  265. state === State.Escape &&
  266. !((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
  267. ) {
  268. state = State.String;
  269. car = sql.charAt(idx++);
  270. continue;
  271. }
  272. switch (car) {
  273. case '*':
  274. if (state === State.Normal && lastChar == '/') {
  275. state = State.SlashStarComment;
  276. }
  277. break;
  278. case '/':
  279. if (state === State.SlashStarComment && lastChar == '*') {
  280. state = State.Normal;
  281. }
  282. break;
  283. case '#':
  284. if (state === State.Normal) {
  285. state = State.EOLComment;
  286. }
  287. break;
  288. case '-':
  289. if (state === State.Normal && lastChar == '-') {
  290. state = State.EOLComment;
  291. }
  292. break;
  293. case '\n':
  294. if (state === State.EOLComment) {
  295. state = State.Normal;
  296. }
  297. break;
  298. case '"':
  299. if (state === State.Normal) {
  300. state = State.String;
  301. singleQuotes = false;
  302. } else if (state === State.String && !singleQuotes) {
  303. state = State.Normal;
  304. } else if (state === State.Escape && !singleQuotes) {
  305. state = State.String;
  306. }
  307. break;
  308. case ';':
  309. if (state === State.Normal) {
  310. semicolon = true;
  311. multipleQueriesPrepare = false;
  312. }
  313. break;
  314. case "'":
  315. if (state === State.Normal) {
  316. state = State.String;
  317. singleQuotes = true;
  318. } else if (state === State.String && singleQuotes) {
  319. state = State.Normal;
  320. } else if (state === State.Escape && singleQuotes) {
  321. state = State.String;
  322. }
  323. break;
  324. case '\\':
  325. if (state === State.String) {
  326. state = State.Escape;
  327. }
  328. break;
  329. case '?':
  330. if (state === State.Normal) {
  331. hasParam = true;
  332. let part = sql.substring(lastParameterPosition, idx - 1);
  333. lastParameterPosition = idx;
  334. if (preValuePart1 === null) {
  335. preValuePart1 = part;
  336. preValuePart2 = '';
  337. } else if (preValuePart2 === null) {
  338. preValuePart2 = part;
  339. } else {
  340. if (postValuePart) {
  341. //having parameters after the last ")" of value is not rewritable
  342. reWritablePrepare = false;
  343. partList.push(postValuePart + part);
  344. postValuePart = null;
  345. } else partList.push(part);
  346. }
  347. }
  348. break;
  349. case '`':
  350. if (state === State.Backtick) {
  351. state = State.Normal;
  352. } else if (state === State.Normal) {
  353. state = State.Backtick;
  354. }
  355. break;
  356. case 's':
  357. case 'S':
  358. if (
  359. state === State.Normal &&
  360. postValuePart === null &&
  361. sql.length > idx + 5 &&
  362. (sql.charAt(idx) === 'e' || sql.charAt(idx) === 'E') &&
  363. (sql.charAt(idx + 1) === 'l' || sql.charAt(idx + 1) === 'L') &&
  364. (sql.charAt(idx + 2) === 'e' || sql.charAt(idx + 2) === 'E') &&
  365. (sql.charAt(idx + 3) === 'c' || sql.charAt(idx + 3) === 'C') &&
  366. (sql.charAt(idx + 4) === 't' || sql.charAt(idx + 4) === 'T')
  367. ) {
  368. //field/table name might contain 'select'
  369. if (
  370. idx > 1 &&
  371. sql.charAt(idx - 2) > ' ' &&
  372. '();><=-+,'.indexOf(sql.charAt(idx - 2)) === -1
  373. ) {
  374. break;
  375. }
  376. if (sql.charAt(idx + 5) > ' ' && '();><=-+,'.indexOf(sql.charAt(idx + 5)) === -1) {
  377. break;
  378. }
  379. //SELECT queries, INSERT FROM SELECT not rewritable
  380. reWritablePrepare = false;
  381. }
  382. break;
  383. case 'v':
  384. case 'V':
  385. if (
  386. state === State.Normal &&
  387. !preValuePart1 &&
  388. (lastChar == ')' || lastChar <= ' ') &&
  389. sql.length > idx + 6 &&
  390. (sql.charAt(idx) === 'a' || sql.charAt(idx) === 'A') &&
  391. (sql.charAt(idx + 1) === 'l' || sql.charAt(idx + 1) === 'L') &&
  392. (sql.charAt(idx + 2) === 'u' || sql.charAt(idx + 2) === 'U') &&
  393. (sql.charAt(idx + 3) === 'e' || sql.charAt(idx + 3) === 'E') &&
  394. (sql.charAt(idx + 4) === 's' || sql.charAt(idx + 4) === 'S') &&
  395. (sql.charAt(idx + 5) === '(' || sql.charAt(idx + 5) <= ' ')
  396. ) {
  397. idx += 5;
  398. preValuePart1 = sql.substring(lastParameterPosition, idx);
  399. lastParameterPosition = idx;
  400. }
  401. break;
  402. case 'l':
  403. case 'L':
  404. if (
  405. state === State.Normal &&
  406. sql.length > idx + 13 &&
  407. (sql.charAt(idx) === 'a' || sql.charAt(idx) === 'A') &&
  408. (sql.charAt(idx + 1) === 's' || sql.charAt(idx + 1) === 'S') &&
  409. (sql.charAt(idx + 2) === 't' || sql.charAt(idx + 2) === 'T') &&
  410. sql.charAt(idx + 3) === '_' &&
  411. (sql.charAt(idx + 4) === 'i' || sql.charAt(idx + 4) === 'I') &&
  412. (sql.charAt(idx + 5) === 'n' || sql.charAt(idx + 5) === 'N') &&
  413. (sql.charAt(idx + 6) === 's' || sql.charAt(idx + 6) === 'S') &&
  414. (sql.charAt(idx + 7) === 'e' || sql.charAt(idx + 7) === 'E') &&
  415. (sql.charAt(idx + 8) === 'r' || sql.charAt(idx + 8) === 'R') &&
  416. (sql.charAt(idx + 9) === 't' || sql.charAt(idx + 9) === 'T') &&
  417. sql.charAt(idx + 10) === '_' &&
  418. (sql.charAt(idx + 11) === 'i' || sql.charAt(idx + 11) === 'I') &&
  419. (sql.charAt(idx + 12) === 'd' || sql.charAt(idx + 12) === 'D') &&
  420. sql.charAt(idx + 13) === '('
  421. ) {
  422. reWritablePrepare = false;
  423. idx += 13;
  424. }
  425. break;
  426. case '(':
  427. if (state === State.Normal) {
  428. isInParenthesis++;
  429. }
  430. break;
  431. case ')':
  432. if (state === State.Normal) {
  433. isInParenthesis--;
  434. if (isInParenthesis === 0 && preValuePart2 !== null && postValuePart === null) {
  435. postValuePart = sql.substring(lastParameterPosition, idx);
  436. lastParameterPosition = idx;
  437. }
  438. }
  439. break;
  440. default:
  441. if (state === State.Normal && isFirstChar && car > ' ') {
  442. if (
  443. (car === 'I' || car === 'i') &&
  444. sql.length > idx + 6 &&
  445. (sql.charAt(idx) === 'n' || sql.charAt(idx) === 'N') &&
  446. (sql.charAt(idx + 1) === 's' || sql.charAt(idx + 1) === 'S') &&
  447. (sql.charAt(idx + 2) === 'e' || sql.charAt(idx + 2) === 'E') &&
  448. (sql.charAt(idx + 3) === 'r' || sql.charAt(idx + 3) === 'R') &&
  449. (sql.charAt(idx + 4) === 't' || sql.charAt(idx + 4) === 'T') &&
  450. (sql.charAt(idx + 5) === '(' || sql.charAt(idx + 5) <= ' ')
  451. ) {
  452. isInsert = true;
  453. }
  454. isFirstChar = false;
  455. }
  456. //multiple queries
  457. if (state === State.Normal && semicolon && car >= ' ') {
  458. reWritablePrepare = false;
  459. multipleQueriesPrepare = true;
  460. }
  461. break;
  462. }
  463. lastChar = car;
  464. car = sql.charAt(idx++);
  465. }
  466. if (state === State.EOLComment) multipleQueriesPrepare = false;
  467. if (!hasParam) {
  468. //permit to have rewrite without parameter
  469. if (preValuePart1 === null) {
  470. partList.unshift('');
  471. partList.unshift(sql);
  472. } else {
  473. partList.unshift(sql.substring(lastParameterPosition, idx));
  474. partList.unshift(preValuePart1);
  475. }
  476. lastParameterPosition = idx;
  477. } else {
  478. partList.unshift(preValuePart2 !== null ? preValuePart2 : '');
  479. partList.unshift(preValuePart1 !== null ? preValuePart1 : '');
  480. }
  481. if (!isInsert) {
  482. reWritablePrepare = false;
  483. }
  484. //postValuePart is the value after the last parameter and parenthesis
  485. //if no param, don't add to the list.
  486. if (hasParam) {
  487. partList.push(postValuePart !== null ? postValuePart : '');
  488. }
  489. partList.push(sql.substring(lastParameterPosition, idx));
  490. return {
  491. partList: partList,
  492. reWritable: reWritablePrepare,
  493. multipleQueries: multipleQueriesPrepare
  494. };
  495. };
  496. module.exports.searchPlaceholder = function (sql, info, initialValues, displaySql) {
  497. let sqlPlaceHolder = '';
  498. const rowNumber = initialValues.length;
  499. let values = new Array(rowNumber);
  500. for (let i = 0; i < rowNumber; i++) values[i] = [];
  501. let state = State.Normal;
  502. let lastChar = '\0';
  503. let singleQuotes = false;
  504. let lastParameterPosition = 0;
  505. let idx = 0;
  506. let car = sql.charAt(idx++);
  507. let placeholderName;
  508. while (car !== '') {
  509. if (
  510. state === State.Escape &&
  511. !((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
  512. ) {
  513. state = State.String;
  514. lastChar = car;
  515. car = sql.charAt(idx++);
  516. continue;
  517. }
  518. switch (car) {
  519. case '*':
  520. if (state === State.Normal && lastChar == '/') state = State.SlashStarComment;
  521. break;
  522. case '/':
  523. if (state === State.SlashStarComment && lastChar == '*') state = State.Normal;
  524. break;
  525. case '#':
  526. if (state === State.Normal) state = State.EOLComment;
  527. break;
  528. case '-':
  529. if (state === State.Normal && lastChar == '-') {
  530. state = State.EOLComment;
  531. }
  532. break;
  533. case '\n':
  534. if (state === State.EOLComment) {
  535. state = State.Normal;
  536. }
  537. break;
  538. case '"':
  539. if (state === State.Normal) {
  540. state = State.String;
  541. singleQuotes = false;
  542. } else if (state === State.String && !singleQuotes) {
  543. state = State.Normal;
  544. } else if (state === State.Escape && !singleQuotes) {
  545. state = State.String;
  546. }
  547. break;
  548. case "'":
  549. if (state === State.Normal) {
  550. state = State.String;
  551. singleQuotes = true;
  552. } else if (state === State.String && singleQuotes) {
  553. state = State.Normal;
  554. singleQuotes = false;
  555. } else if (state === State.Escape && singleQuotes) {
  556. state = State.String;
  557. }
  558. break;
  559. case '\\':
  560. if (state === State.String) state = State.Escape;
  561. break;
  562. case ':':
  563. if (state === State.Normal) {
  564. sqlPlaceHolder += sql.substring(lastParameterPosition, idx - 1) + '?';
  565. placeholderName = '';
  566. while (
  567. ((car = sql.charAt(idx++)) !== '' && car >= '0' && car <= '9') ||
  568. (car >= 'A' && car <= 'Z') ||
  569. (car >= 'a' && car <= 'z') ||
  570. car === '-' ||
  571. car === '_'
  572. ) {
  573. placeholderName += car;
  574. }
  575. idx--;
  576. for (let i = 0; i < rowNumber; i++) {
  577. const val = initialValues[i][placeholderName];
  578. if (val !== undefined) {
  579. values[i].push(val);
  580. } else {
  581. values[i].push(null);
  582. }
  583. }
  584. lastParameterPosition = idx;
  585. }
  586. break;
  587. case '`':
  588. if (state === State.Backtick) {
  589. state = State.Normal;
  590. } else if (state === State.Normal) {
  591. state = State.Backtick;
  592. }
  593. }
  594. lastChar = car;
  595. car = sql.charAt(idx++);
  596. }
  597. if (lastParameterPosition === 0) {
  598. sqlPlaceHolder = sql;
  599. } else {
  600. sqlPlaceHolder += sql.substring(lastParameterPosition);
  601. }
  602. return { sql: sqlPlaceHolder, values: values };
  603. };
  604. /**
  605. * Split query according to named parameters.
  606. *
  607. * The only rewritten queries follow these notation: INSERT [LOW_PRIORITY | DELAYED |
  608. * HIGH_PRIORITY] [IGNORE] [INTO] tbl_name [PARTITION (partition_list)] [(col,...)] {VALUES |
  609. * VALUE} (...) [ ON DUPLICATE KEY UPDATE col=expr [, col=expr] ... ] With expr without
  610. * parameter.
  611. *
  612. * Query with INSERT ... SELECT / containing LAST_INSERT_ID() will not be rewritten
  613. *
  614. * query parts will be split this way :
  615. * - pre-value part
  616. * - after value part
  617. * [- after parameter part] (after each parameter)
  618. * - ending part
  619. *
  620. * example : INSERT INTO MyTABLE VALUES (9, :param1, 5, :param2, 8) ON DUPLICATE KEY UPDATE col2=col2+10
  621. * will result in :
  622. * - pre-value : "INSERT INTO MyTABLE VALUES"
  623. * - after value : " (9, "
  624. * - after parameter : ", 5, "
  625. * - after parameter : ", 8)"
  626. * - ending : " ON DUPLICATE KEY UPDATE col2=col2+10"
  627. *
  628. *
  629. * @returns {JSON} query separated by parameters
  630. */
  631. module.exports.splitRewritableNamedParameterQuery = function (sql, initialValues) {
  632. let reWritablePrepare = true;
  633. let multipleQueriesPrepare = true;
  634. let partList = [];
  635. let values = new Array(initialValues.length);
  636. for (let i = 0; i < values.length; i++) values[i] = [];
  637. let lastChar = '\0';
  638. let lastParameterPosition = 0;
  639. let preValuePart1 = null;
  640. let preValuePart2 = null;
  641. let postValuePart = null;
  642. let singleQuotes = false;
  643. let isInParenthesis = 0;
  644. let isFirstChar = true;
  645. let isInsert = false;
  646. let semicolon = false;
  647. let hasParam = false;
  648. let placeholderName;
  649. let state = State.Normal;
  650. let idx = 0;
  651. let car = sql.charAt(idx++);
  652. while (car !== '') {
  653. if (
  654. state === State.Escape &&
  655. !((car === "'" && singleQuotes) || (car === '"' && !singleQuotes))
  656. ) {
  657. state = State.String;
  658. car = sql.charAt(idx++);
  659. continue;
  660. }
  661. switch (car) {
  662. case '*':
  663. if (state === State.Normal && lastChar == '/') {
  664. state = State.SlashStarComment;
  665. }
  666. break;
  667. case '/':
  668. if (state === State.SlashStarComment && lastChar == '*') {
  669. state = State.Normal;
  670. }
  671. break;
  672. case '#':
  673. if (state === State.Normal) {
  674. state = State.EOLComment;
  675. }
  676. break;
  677. case '-':
  678. if (state === State.Normal && lastChar == '-') {
  679. state = State.EOLComment;
  680. }
  681. break;
  682. case '\n':
  683. if (state === State.EOLComment) {
  684. state = State.Normal;
  685. }
  686. break;
  687. case '"':
  688. if (state === State.Normal) {
  689. state = State.String;
  690. singleQuotes = false;
  691. } else if (state === State.String && !singleQuotes) {
  692. state = State.Normal;
  693. } else if (state === State.Escape && !singleQuotes) {
  694. state = State.String;
  695. }
  696. break;
  697. case ';':
  698. if (state === State.Normal) {
  699. semicolon = true;
  700. multipleQueriesPrepare = false;
  701. }
  702. break;
  703. case "'":
  704. if (state === State.Normal) {
  705. state = State.String;
  706. singleQuotes = true;
  707. } else if (state === State.String && singleQuotes) {
  708. state = State.Normal;
  709. } else if (state === State.Escape && singleQuotes) {
  710. state = State.String;
  711. }
  712. break;
  713. case '\\':
  714. if (state === State.String) {
  715. state = State.Escape;
  716. }
  717. break;
  718. case ':':
  719. if (state === State.Normal) {
  720. let part = sql.substring(lastParameterPosition, idx - 1);
  721. placeholderName = '';
  722. while (
  723. ((car = sql.charAt(idx++)) !== '' && car >= '0' && car <= '9') ||
  724. (car >= 'A' && car <= 'Z') ||
  725. (car >= 'a' && car <= 'z') ||
  726. car === '-' ||
  727. car === '_'
  728. ) {
  729. placeholderName += car;
  730. }
  731. idx--;
  732. hasParam = true;
  733. initialValues.forEach((row, idx) => {
  734. if (row[placeholderName] !== undefined) {
  735. values[idx].push(row[placeholderName]);
  736. } else {
  737. values[idx].push(null);
  738. }
  739. });
  740. lastParameterPosition = idx;
  741. if (preValuePart1 === null) {
  742. preValuePart1 = part;
  743. preValuePart2 = '';
  744. } else if (preValuePart2 === null) {
  745. preValuePart2 = part;
  746. } else {
  747. if (postValuePart) {
  748. //having parameters after the last ")" of value is not rewritable
  749. reWritablePrepare = false;
  750. partList.push(postValuePart + part);
  751. postValuePart = null;
  752. } else partList.push(part);
  753. }
  754. }
  755. break;
  756. case '`':
  757. if (state === State.Backtick) {
  758. state = State.Normal;
  759. } else if (state === State.Normal) {
  760. state = State.Backtick;
  761. }
  762. break;
  763. case 's':
  764. case 'S':
  765. if (
  766. state === State.Normal &&
  767. postValuePart === null &&
  768. sql.length > idx + 5 &&
  769. (sql.charAt(idx) === 'e' || sql.charAt(idx) === 'E') &&
  770. (sql.charAt(idx + 1) === 'l' || sql.charAt(idx + 1) === 'L') &&
  771. (sql.charAt(idx + 2) === 'e' || sql.charAt(idx + 2) === 'E') &&
  772. (sql.charAt(idx + 3) === 'c' || sql.charAt(idx + 3) === 'C') &&
  773. (sql.charAt(idx + 4) === 't' || sql.charAt(idx + 4) === 'T')
  774. ) {
  775. //field/table name might contain 'select'
  776. if (
  777. idx > 1 &&
  778. sql.charAt(idx - 2) > ' ' &&
  779. '();><=-+,'.indexOf(sql.charAt(idx - 2)) === -1
  780. ) {
  781. break;
  782. }
  783. if (sql.charAt(idx + 5) > ' ' && '();><=-+,'.indexOf(sql.charAt(idx + 5)) === -1) {
  784. break;
  785. }
  786. //SELECT queries, INSERT FROM SELECT not rewritable
  787. reWritablePrepare = false;
  788. }
  789. break;
  790. case 'v':
  791. case 'V':
  792. if (
  793. state === State.Normal &&
  794. !preValuePart1 &&
  795. (lastChar == ')' || lastChar <= ' ') &&
  796. sql.length > idx + 6 &&
  797. (sql.charAt(idx) === 'a' || sql.charAt(idx) === 'A') &&
  798. (sql.charAt(idx + 1) === 'l' || sql.charAt(idx + 1) === 'L') &&
  799. (sql.charAt(idx + 2) === 'u' || sql.charAt(idx + 2) === 'U') &&
  800. (sql.charAt(idx + 3) === 'e' || sql.charAt(idx + 3) === 'E') &&
  801. (sql.charAt(idx + 4) === 's' || sql.charAt(idx + 4) === 'S') &&
  802. (sql.charAt(idx + 5) === '(' || sql.charAt(idx + 5) <= ' ')
  803. ) {
  804. idx += 5;
  805. preValuePart1 = sql.substring(lastParameterPosition, idx);
  806. lastParameterPosition = idx;
  807. }
  808. break;
  809. case 'l':
  810. case 'L':
  811. if (
  812. state === State.Normal &&
  813. sql.length > idx + 13 &&
  814. (sql.charAt(idx) === 'a' || sql.charAt(idx) === 'A') &&
  815. (sql.charAt(idx + 1) === 's' || sql.charAt(idx + 1) === 'S') &&
  816. (sql.charAt(idx + 2) === 't' || sql.charAt(idx + 2) === 'T') &&
  817. sql.charAt(idx + 3) === '_' &&
  818. (sql.charAt(idx + 4) === 'i' || sql.charAt(idx + 4) === 'I') &&
  819. (sql.charAt(idx + 5) === 'n' || sql.charAt(idx + 5) === 'N') &&
  820. (sql.charAt(idx + 6) === 's' || sql.charAt(idx + 6) === 'S') &&
  821. (sql.charAt(idx + 7) === 'e' || sql.charAt(idx + 7) === 'E') &&
  822. (sql.charAt(idx + 8) === 'r' || sql.charAt(idx + 8) === 'R') &&
  823. (sql.charAt(idx + 9) === 't' || sql.charAt(idx + 9) === 'T') &&
  824. sql.charAt(idx + 10) === '_' &&
  825. (sql.charAt(idx + 11) === 'i' || sql.charAt(idx + 11) === 'I') &&
  826. (sql.charAt(idx + 12) === 'd' || sql.charAt(idx + 12) === 'D') &&
  827. sql.charAt(idx + 13) === '('
  828. ) {
  829. reWritablePrepare = false;
  830. idx += 13;
  831. }
  832. break;
  833. case '(':
  834. if (state === State.Normal) {
  835. isInParenthesis++;
  836. }
  837. break;
  838. case ')':
  839. if (state === State.Normal) {
  840. isInParenthesis--;
  841. if (isInParenthesis === 0 && preValuePart2 !== null && postValuePart === null) {
  842. postValuePart = sql.substring(lastParameterPosition, idx);
  843. lastParameterPosition = idx;
  844. }
  845. }
  846. break;
  847. default:
  848. if (state === State.Normal && isFirstChar && car > ' ') {
  849. if (
  850. (car === 'I' || car === 'i') &&
  851. sql.length > idx + 6 &&
  852. (sql.charAt(idx) === 'n' || sql.charAt(idx) === 'N') &&
  853. (sql.charAt(idx + 1) === 's' || sql.charAt(idx + 1) === 'S') &&
  854. (sql.charAt(idx + 2) === 'e' || sql.charAt(idx + 2) === 'E') &&
  855. (sql.charAt(idx + 3) === 'r' || sql.charAt(idx + 3) === 'R') &&
  856. (sql.charAt(idx + 4) === 't' || sql.charAt(idx + 4) === 'T') &&
  857. (sql.charAt(idx + 5) === '(' || sql.charAt(idx + 5) <= ' ')
  858. ) {
  859. isInsert = true;
  860. }
  861. isFirstChar = false;
  862. }
  863. //multiple queries
  864. if (state === State.Normal && semicolon && car >= ' ') {
  865. reWritablePrepare = false;
  866. multipleQueriesPrepare = true;
  867. }
  868. break;
  869. }
  870. lastChar = car;
  871. car = sql.charAt(idx++);
  872. }
  873. if (state === State.EOLComment) multipleQueriesPrepare = false;
  874. if (!hasParam) {
  875. //permit to have rewrite without parameter
  876. if (preValuePart1 === null) {
  877. partList.unshift('');
  878. partList.unshift(sql);
  879. } else {
  880. partList.unshift(sql.substring(lastParameterPosition, idx));
  881. partList.unshift(preValuePart1);
  882. }
  883. lastParameterPosition = idx;
  884. } else {
  885. partList.unshift(preValuePart2 !== null ? preValuePart2 : '');
  886. partList.unshift(preValuePart1 !== null ? preValuePart1 : '');
  887. }
  888. if (!isInsert) {
  889. reWritablePrepare = false;
  890. }
  891. //postValuePart is the value after the last parameter and parenthesis
  892. //if no param, don't add to the list.
  893. if (hasParam) {
  894. partList.push(postValuePart !== null ? postValuePart : '');
  895. }
  896. partList.push(sql.substring(lastParameterPosition, idx));
  897. return {
  898. partList: partList,
  899. reWritable: reWritablePrepare,
  900. multipleQueries: multipleQueriesPrepare,
  901. values: values
  902. };
  903. };
  904. /**
  905. * Ensure that filename requested by server corresponds to query
  906. * protocol : https://mariadb.com/kb/en/library/local_infile-packet/
  907. *
  908. * @param sql query
  909. * @param parameters parameters if any
  910. * @param fileName server requested file
  911. * @returns {boolean} is filename corresponding to query
  912. */
  913. module.exports.validateFileName = function (sql, parameters, fileName) {
  914. let queryValidator = new RegExp(
  915. "^(\\s*\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*\\s*LOAD\\s+DATA\\s+((LOW_PRIORITY|CONCURRENT)\\s+)?LOCAL\\s+INFILE\\s+'" +
  916. fileName +
  917. "'",
  918. 'i'
  919. );
  920. if (queryValidator.test(sql)) return true;
  921. if (parameters != null) {
  922. queryValidator = new RegExp(
  923. '^(\\s*\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*\\s*LOAD\\s+DATA\\s+((LOW_PRIORITY|CONCURRENT)\\s+)?LOCAL\\s+INFILE\\s+\\?',
  924. 'i'
  925. );
  926. if (queryValidator.test(sql) && parameters.length > 0) {
  927. return parameters[0].toLowerCase() === fileName.toLowerCase();
  928. }
  929. }
  930. return false;
  931. };