Skip to content

Commit 12e2258

Browse files
authored
Merge pull request #1263 from HubSpot/handle-escaping-and-unescaping-in-tokenizer
Fix helper token escape handling and unescaping when unquoting strings
2 parents 43bbea7 + a5d85ad commit 12e2258

File tree

7 files changed

+56
-5
lines changed

7 files changed

+56
-5
lines changed

src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ public Object resolveObject(String variable, int lineNumber, int startPosition)
692692
return "";
693693
}
694694
if (WhitespaceUtils.isQuoted(variable)) {
695-
return WhitespaceUtils.unquote(variable);
695+
return WhitespaceUtils.unquoteAndUnescape(variable);
696696
} else {
697697
Object val = retraceVariable(variable, lineNumber, startPosition);
698698
if (val == null) {

src/main/java/com/hubspot/jinjava/lib/tag/BlockTag.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public OutputNode interpretOutput(TagNode tagNode, JinjavaInterpreter interprete
7070
);
7171
}
7272

73-
String blockName = WhitespaceUtils.unquote(tagData.next());
73+
String blockName = WhitespaceUtils.unquoteAndUnescape(tagData.next());
7474

7575
interpreter.addBlock(
7676
blockName,

src/main/java/com/hubspot/jinjava/util/HelperStringTokenizer.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class HelperStringTokenizer extends AbstractIterator<String> {
3636
private boolean useComma = false;
3737
private char quoteChar = 0;
3838
private boolean inQuote = false;
39+
private boolean isEscaped = false;
3940

4041
public HelperStringTokenizer(String s) {
4142
value = s.toCharArray();
@@ -71,7 +72,8 @@ protected String computeNext() {
7172

7273
private String makeToken() {
7374
char c = value[currPost++];
74-
if (c == '"' || c == '\'') {
75+
76+
if ((c == '"' || c == '\'') && !isEscaped) {
7577
if (inQuote) {
7678
if (quoteChar == c) {
7779
inQuote = false;
@@ -81,6 +83,9 @@ private String makeToken() {
8183
quoteChar = c;
8284
}
8385
}
86+
87+
isEscaped = (c == '\\' && !isEscaped);
88+
8489
if ((Character.isWhitespace(c) || (useComma && c == ',')) && !inQuote) {
8590
return newToken();
8691
}

src/main/java/com/hubspot/jinjava/util/WhitespaceUtils.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ public static String unquote(String s) {
104104
return s.trim();
105105
}
106106

107-
// TODO see if all usages of unquote can use this method instead
108107
public static String unquoteAndUnescape(String s) {
109108
if (Strings.isNullOrEmpty(s)) {
110109
return "";

src/test/java/com/hubspot/jinjava/lib/tag/CycleTagTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,11 @@ public void itDefaultsMultipleNullToImageUsingAs() {
3232
"{% for item in [0,1] %}{% cycle {{foo}},{{bar}} as var %}{% cycle var %}{% endfor %}";
3333
assertThat(interpreter.render(template)).isEqualTo("{{foo}}{{bar}}");
3434
}
35+
36+
@Test
37+
public void itHandlesEscapedQuotes() {
38+
String template =
39+
"{% for item in [0,1] %}{% cycle 'a','class=\\'foo bar\\'' %}.{% endfor %}";
40+
assertThat(interpreter.render(template)).isEqualTo("a.class='foo bar'.");
41+
}
3542
}

src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerCycleTagTest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.hubspot.jinjava.lib.tag.Tag;
1111
import com.hubspot.jinjava.mode.EagerExecutionMode;
1212
import com.hubspot.jinjava.tree.parse.TagToken;
13+
import java.util.List;
1314
import java.util.Optional;
1415
import org.junit.After;
1516
import org.junit.Before;
@@ -65,9 +66,22 @@ public void itAddCycleTagAsADeferredToken() {
6566

6667
@Test
6768
public void itHandlesDeferredCycle() {
68-
interpreter.getContext().put("deferred", DeferredValue.instance());
6969
String template =
7070
"{% set l = [] %}{% for item in deferred %}{% cycle l.append(deferred),5 %}{% endfor %}{{ l }}";
7171
assertThat(interpreter.render(template)).isEqualTo(template);
7272
}
73+
74+
@Test
75+
public void iitHandlesEscapedQuotesInVariable() {
76+
String template =
77+
"{% set class = \"class='foo bar'\" %}{% for item in deferred %}{% cycle 'item-1',class %}.{% endfor %}";
78+
String firstPass = interpreter.render(template);
79+
assertThat(firstPass)
80+
.isEqualTo(
81+
"{% for item in deferred %}{% cycle 'item-1','class=\\'foo bar\\'' %}.{% endfor %}"
82+
);
83+
interpreter.getContext().put("deferred", List.of(0, 1));
84+
String secondPass = interpreter.render(firstPass);
85+
assertThat(secondPass).isEqualTo("item-1.class='foo bar'.");
86+
}
7387
}

src/test/java/com/hubspot/jinjava/util/HelperStringTokenizerTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,30 @@ public void itDoesntReturnTrailingNull() {
112112
.containsExactly("product", "in", "collections.frontpage.products")
113113
.doesNotContainNull();
114114
}
115+
116+
@Test
117+
public void itHandlesEscapedQuotesWithinQuotedStrings() {
118+
assertThat(
119+
new HelperStringTokenizer("'hi','y\\'all don\\'t'").splitComma(true).allTokens()
120+
)
121+
.containsExactly("'hi'", "'y\\'all don\\'t'");
122+
}
123+
124+
@Test
125+
public void itHandlesEscapedDoubleQuotesWithinQuotedStrings() {
126+
assertThat(
127+
new HelperStringTokenizer("\"hi\",\"say \\\"hello\\\"\"")
128+
.splitComma(true)
129+
.allTokens()
130+
)
131+
.containsExactly("\"hi\"", "\"say \\\"hello\\\"\"");
132+
}
133+
134+
@Test
135+
public void itHandlesEscapedBackslashes() {
136+
assertThat(
137+
new HelperStringTokenizer("'path\\\\to\\file'").splitComma(true).allTokens()
138+
)
139+
.containsExactly("'path\\\\to\\file'");
140+
}
115141
}

0 commit comments

Comments
 (0)