1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | 'use strict';
|
21 |
|
22 | const {
|
23 | CHARS_GLOBAL_REGEXP,
|
24 | escapeSeries,
|
25 | isSeries,
|
26 | isSqlFragment,
|
27 | makeEscaper,
|
28 | } = require('./escapers.js');
|
29 |
|
30 | const { toString: bufferProtoToString } = Buffer.prototype;
|
31 | const { isBuffer } = Buffer;
|
32 | const { apply } = Reflect;
|
33 |
|
34 | const QUAL_GLOBAL_REGEXP = /\./g;
|
35 | const PG_ID_REGEXP = /^(?:"(?:[^"]|"")+"|u&"(?:[^"\\]|""|\\.)+")$/i;
|
36 | const PG_QUAL_ID_REGEXP = /^(?:(?:"(?:[^"]|"")+"|u&"(?:[^"\\]|""|\\.)+")(?:[.](?!$)|$))+$/;
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | const PG_CHARS_ESCAPE_MAP = {
|
45 | __proto__: null,
|
46 |
|
47 | '\0': '',
|
48 | '\b': '\b',
|
49 | '\t': '\t',
|
50 | '\n': '\n',
|
51 | '\r': '\r',
|
52 | '\x1a': '\x1a',
|
53 | '"': '"',
|
54 | '$': '$',
|
55 | '\'': '\'\'',
|
56 | '\\': '\\',
|
57 | };
|
58 |
|
59 | const PG_ID_ESCAPE_MAP = {
|
60 | __proto__: null,
|
61 |
|
62 | '\0': '',
|
63 | '\b': '\b',
|
64 | '\t': '\t',
|
65 | '\n': '\n',
|
66 | '\r': '\r',
|
67 | '\x1a': '\x1a',
|
68 | '"': '""',
|
69 | '$': '$',
|
70 | '\'': '\'',
|
71 | '\\': '\\',
|
72 | };
|
73 |
|
74 | const PG_E_CHARS_ESCAPE_MAP = {
|
75 | __proto__: null,
|
76 |
|
77 | '\0': '',
|
78 | '\b': '\\b',
|
79 | '\t': '\\t',
|
80 | '\n': '\\n',
|
81 | '\r': '\\r',
|
82 | '\x1a': '\\x1a',
|
83 | '"': '\\"',
|
84 | '$': '\\$',
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | '\'': '\'\'',
|
93 | '\\': '\\\\',
|
94 | };
|
95 |
|
96 | const PG_U_CHARS_ESCAPE_MAP = {
|
97 | __proto__: null,
|
98 |
|
99 | '\0': '',
|
100 | '\b': '\\0008',
|
101 | '\t': '\\0009',
|
102 | '\n': '\\000a',
|
103 | '\r': '\\000d',
|
104 | '\x1a': '\\001a',
|
105 | '"': '\\0022',
|
106 | '$': '\\0024',
|
107 | '\'': '\\0027',
|
108 | '\\': '\\005c',
|
109 | };
|
110 |
|
111 | const HEX_GLOBAL_REGEXP = /[0-9A-Fa-f]/g;
|
112 | const HEX_TO_BINARY_TABLE = {
|
113 | __proto__: null,
|
114 | '0': '0000',
|
115 | '1': '0001',
|
116 | '2': '0010',
|
117 | '3': '0011',
|
118 | '4': '0100',
|
119 | '5': '0101',
|
120 | '6': '0110',
|
121 | '7': '0111',
|
122 | '8': '1000',
|
123 | '9': '1001',
|
124 | 'A': '1010',
|
125 | 'B': '1011',
|
126 | 'C': '1100',
|
127 | 'D': '1101',
|
128 | 'E': '1110',
|
129 | 'F': '1111',
|
130 | 'a': '1010',
|
131 | 'b': '1011',
|
132 | 'c': '1100',
|
133 | 'd': '1101',
|
134 | 'e': '1110',
|
135 | 'f': '1111',
|
136 | };
|
137 |
|
138 | function hexDigitToBinary(digit) {
|
139 | return HEX_TO_BINARY_TABLE[digit];
|
140 | }
|
141 |
|
142 | function hexToBinary(str) {
|
143 | return str.replace(HEX_GLOBAL_REGEXP, hexDigitToBinary);
|
144 | }
|
145 |
|
146 | function pgEscapeStringBody(str, escapeMap) {
|
147 | let chunkIndex = 0;
|
148 | let escapedVal = '';
|
149 |
|
150 | CHARS_GLOBAL_REGEXP.lastIndex = 0;
|
151 | for (let match; (match = CHARS_GLOBAL_REGEXP.exec(str));) {
|
152 | escapedVal += str.substring(chunkIndex, match.index) + escapeMap[match[0]];
|
153 | chunkIndex = CHARS_GLOBAL_REGEXP.lastIndex;
|
154 | }
|
155 |
|
156 | if (chunkIndex === 0) {
|
157 |
|
158 | return str;
|
159 | }
|
160 |
|
161 | if (chunkIndex < str.length) {
|
162 | escapedVal += str.substring(chunkIndex);
|
163 | }
|
164 |
|
165 | return escapedVal;
|
166 | }
|
167 |
|
168 | function pgEscapeId(val, forbidQualified, unicode) {
|
169 | if (isSqlFragment(val)) {
|
170 | const { content } = val;
|
171 | if ((forbidQualified ? PG_ID_REGEXP : PG_QUAL_ID_REGEXP).test(content)) {
|
172 | return content;
|
173 | }
|
174 | throw new Error(`Expected id, got ${ content }`);
|
175 | }
|
176 | if (isSeries(val)) {
|
177 | return escapeSeries(val, (element) => pgEscapeId(element, forbidQualified, unicode), false);
|
178 | }
|
179 | let escaped = unicode ?
|
180 | pgEscapeStringBody(`${ val }`, PG_U_CHARS_ESCAPE_MAP) :
|
181 | pgEscapeStringBody(`${ val }`, PG_ID_ESCAPE_MAP);
|
182 | if (!forbidQualified) {
|
183 | escaped = escaped.replace(QUAL_GLOBAL_REGEXP, unicode ? '".u&"' : '"."');
|
184 | }
|
185 | return `${ unicode ? 'u&"' : '"' }${ escaped }"`;
|
186 | }
|
187 |
|
188 | const PG_ID_DELIMS_REGEXP = /^(?:[Uu]&)?"|"$/g;
|
189 |
|
190 | function pgEscapeString(val) {
|
191 | const str = `${ val }`;
|
192 |
|
193 | const escapedVal = pgEscapeStringBody(val, PG_E_CHARS_ESCAPE_MAP);
|
194 |
|
195 | if (escapedVal === str) {
|
196 | return `'${ escapedVal }'`;
|
197 | }
|
198 |
|
199 |
|
200 |
|
201 |
|
202 | return `e'${ escapedVal }'`;
|
203 | }
|
204 |
|
205 | const pgEscape = makeEscaper(pgEscapeId, pgEscapeString);
|
206 |
|
207 | function pgEscapeDelimitedString(strValue, delimiter) {
|
208 | switch (delimiter) {
|
209 | case '\'':
|
210 | case 'b\'':
|
211 | case 'x\'':
|
212 | return pgEscapeStringBody(strValue, PG_CHARS_ESCAPE_MAP);
|
213 | case 'e\'':
|
214 | return pgEscapeStringBody(strValue, PG_E_CHARS_ESCAPE_MAP);
|
215 | case 'e':
|
216 | return `'${ pgEscapeStringBody(strValue, PG_E_CHARS_ESCAPE_MAP) }'`;
|
217 | case 'u&\'':
|
218 | return pgEscapeStringBody(strValue, PG_U_CHARS_ESCAPE_MAP);
|
219 | default:
|
220 | break;
|
221 | }
|
222 |
|
223 | if (delimiter[0] === '$' && delimiter.indexOf('$', 1) === delimiter.length - 1) {
|
224 |
|
225 | let embedHazard = strValue.indexOf(delimiter) >= 0;
|
226 | if (!embedHazard) {
|
227 | const lastDollar = strValue.lastIndexOf('$');
|
228 | if (lastDollar >= 0) {
|
229 | const tail = strValue.substring(lastDollar);
|
230 | embedHazard = (tail === delimiter.substring(0, tail.length));
|
231 | }
|
232 | }
|
233 | if (embedHazard) {
|
234 | throw new Error(`Cannot embed ${ JSON.stringify(strValue) } between ${ delimiter }`);
|
235 | }
|
236 | return strValue;
|
237 | }
|
238 | throw new Error(`Cannot escape with ${ delimiter }`);
|
239 | }
|
240 |
|
241 | function pgEscapeDelimited(value, delimiter, timeZone, forbidQualified) {
|
242 | if (delimiter === '"') {
|
243 | return pgEscapeId(value, forbidQualified, false).replace(PG_ID_DELIMS_REGEXP, '');
|
244 | } else if (delimiter === 'u&"') {
|
245 | return pgEscapeId(value, forbidQualified, true).replace(PG_ID_DELIMS_REGEXP, '');
|
246 | }
|
247 |
|
248 | let strValue = value;
|
249 | if (isBuffer(value)) {
|
250 | const wantsBinaryDigits = delimiter === 'b\'';
|
251 | const encoding = wantsBinaryDigits || delimiter === 'x\'' ? 'hex' : 'binary';
|
252 | strValue = apply(bufferProtoToString, value, [ encoding ]);
|
253 | if (wantsBinaryDigits) {
|
254 |
|
255 |
|
256 | strValue = hexToBinary(strValue);
|
257 | }
|
258 | }
|
259 | return pgEscapeDelimitedString(`${ strValue }`, delimiter);
|
260 | }
|
261 |
|
262 | module.exports = Object.freeze({
|
263 | escape: pgEscape,
|
264 | escapeId: pgEscapeId,
|
265 | escapeDelimited: pgEscapeDelimited,
|
266 | });
|