Skip to content

Commit 877df9f

Browse files
committed
address string merge hazards
1 parent c021a92 commit 877df9f

File tree

2 files changed

+58
-11
lines changed

2 files changed

+58
-11
lines changed

lib/es6/Template.js

+26-8
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,14 @@ function interpolateSqlIntoFragment (
7171
const delimiter = delimiters[i - 1];
7272
const value = values[i - 1];
7373

74-
if (delimiter) {
75-
result += escapeDelimitedValue(
76-
value, delimiter, timeZone, forbidQualified);
77-
} else {
78-
result += SqlString.escape(value, stringifyObjects, timeZone);
79-
}
80-
81-
result += chunk;
74+
let escaped = delimiter
75+
? escapeDelimitedValue(value, delimiter, timeZone, forbidQualified)
76+
: defangMergeHazard(
77+
result,
78+
SqlString.escape(value, stringifyObjects, timeZone),
79+
chunk);
80+
81+
result += escaped + chunk;
8282
}
8383

8484
return SqlString.raw(result);
@@ -95,6 +95,24 @@ function escapeDelimitedValue (value, delimiter, timeZone, forbidQualified) {
9595
return escaped.substring(1, escaped.length - 1);
9696
}
9797

98+
function defangMergeHazard (before, escaped, after) {
99+
const escapedLast = escaped[escaped.length - 1];
100+
if ('\"\'`'.indexOf(escapedLast) < 0) {
101+
// Not a merge hazard.
102+
return escaped;
103+
}
104+
105+
let escapedSetOff = escaped;
106+
const lastBefore = before[before.length - 1];
107+
if (escapedLast === escaped[0] && escapedLast === lastBefore) {
108+
escapedSetOff = ' ' + escapedSetOff;
109+
}
110+
if (escapedLast === after[0]) {
111+
escapedSetOff += ' ';
112+
}
113+
return escapedSetOff;
114+
}
115+
98116
/**
99117
* Template tag function that contextually autoescapes values
100118
* producing a SqlFragment.

test/unit/es6/Template.js

+32-3
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,37 @@ test('template tag', {
176176
// Ending in a comment is a concatenation hazard.
177177
// See comments in lib/es6/Lexer.js.
178178
assert.throws(() => sql`SELECT (${0}) -- comment`);
179+
},
180+
'merge-word-string': function () {
181+
runTagTest(
182+
`SELECT utf8'foo'`,
183+
() => sql`SELECT utf8${'foo'}`);
184+
},
185+
'merge-string-string': function () {
186+
runTagTest(
187+
// Adjacent string tokens are concatenated, but 'a''b' is a
188+
// 3-char string with a single-quote in the middle.
189+
`SELECT 'a' 'b'`,
190+
() => sql`SELECT ${'a'}${'b'}`);
191+
},
192+
'merge-bq-bq': function () {
193+
runTagTest(
194+
'SELECT `a` `b`',
195+
() => sql`SELECT ${SqlString.identifier('a')}${SqlString.identifier('b')}`);
196+
},
197+
'merge-static-string-string': function () {
198+
runTagTest(
199+
`SELECT 'a' 'b'`,
200+
() => sql`SELECT 'a'${'b'}`);
201+
},
202+
'merge-string-static-string': function () {
203+
runTagTest(
204+
`SELECT 'a' 'b'`,
205+
() => sql`SELECT ${'a'}'b'`);
206+
},
207+
'not-a-merge-hazard': function () {
208+
runTagTest(
209+
`SELECT 'a''b'`,
210+
() => sql`SELECT 'a''b'`);
179211
}
180-
181-
// TODO: is there a token-merging hazard in sql`SELECT 'x'${'y'}`?
182-
// A literal ' shows up in the decoded output of a single string literal.
183212
});

0 commit comments

Comments
 (0)