Skip to content

Commit 264ac2b

Browse files
authored
Merge pull request #1123 from HubSpot/prevent-accidental-expressions
Prevent accidental expressions from being output
2 parents 7a60dca + 160c254 commit 264ac2b

3 files changed

Lines changed: 128 additions & 0 deletions

File tree

src/main/java/com/hubspot/jinjava/tree/output/OutputList.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
44
import com.hubspot.jinjava.interpret.OutputTooBigException;
55
import com.hubspot.jinjava.interpret.TemplateError;
6+
import com.hubspot.jinjava.tree.parse.TokenScannerSymbols;
67
import com.hubspot.jinjava.util.LengthLimitingStringBuilder;
78
import java.util.LinkedList;
89
import java.util.List;
910

1011
public class OutputList {
12+
public static final String PREVENT_ACCIDENTAL_EXPRESSIONS =
13+
"PREVENT_ACCIDENTAL_EXPRESSIONS";
1114
private final List<OutputNode> nodes = new LinkedList<>();
1215
private final List<BlockPlaceholderOutputNode> blocks = new LinkedList<>();
1316
private final long maxOutputSize;
@@ -48,6 +51,72 @@ public List<BlockPlaceholderOutputNode> getBlocks() {
4851
public String getValue() {
4952
LengthLimitingStringBuilder val = new LengthLimitingStringBuilder(maxOutputSize);
5053

54+
return JinjavaInterpreter
55+
.getCurrentMaybe()
56+
.map(JinjavaInterpreter::getConfig)
57+
.filter(
58+
config ->
59+
config
60+
.getFeatures()
61+
.getActivationStrategy(PREVENT_ACCIDENTAL_EXPRESSIONS)
62+
.isActive(null)
63+
)
64+
.map(
65+
config -> joinNodesWithoutAddingExpressions(val, config.getTokenScannerSymbols())
66+
)
67+
.orElseGet(() -> joinNodes(val));
68+
}
69+
70+
private String joinNodesWithoutAddingExpressions(
71+
LengthLimitingStringBuilder val,
72+
TokenScannerSymbols tokenScannerSymbols
73+
) {
74+
String separator = getWhitespaceSeparator(tokenScannerSymbols);
75+
String prev = null;
76+
String cur;
77+
for (OutputNode node : nodes) {
78+
try {
79+
cur = node.getValue();
80+
if (
81+
prev != null &&
82+
prev.length() > 0 &&
83+
prev.charAt(prev.length() - 1) == tokenScannerSymbols.getExprStartChar()
84+
) {
85+
if (
86+
cur.length() > 0 &&
87+
TokenScannerSymbols.isNoteTagOrExprChar(tokenScannerSymbols, cur.charAt(0))
88+
) {
89+
val.append(separator);
90+
}
91+
}
92+
prev = cur;
93+
val.append(node.getValue());
94+
} catch (OutputTooBigException e) {
95+
JinjavaInterpreter
96+
.getCurrent()
97+
.addError(TemplateError.fromOutputTooBigException(e));
98+
return val.toString();
99+
}
100+
}
101+
102+
return val.toString();
103+
}
104+
105+
private static String getWhitespaceSeparator(TokenScannerSymbols tokenScannerSymbols) {
106+
@SuppressWarnings("StringBufferReplaceableByString")
107+
String separator = new StringBuilder()
108+
.append('\n')
109+
.append(tokenScannerSymbols.getPrefixChar())
110+
.append(tokenScannerSymbols.getNoteChar())
111+
.append(tokenScannerSymbols.getTrimChar())
112+
.append(' ')
113+
.append(tokenScannerSymbols.getNoteChar())
114+
.append(tokenScannerSymbols.getExprEndChar())
115+
.toString();
116+
return separator;
117+
}
118+
119+
private String joinNodes(LengthLimitingStringBuilder val) {
51120
for (OutputNode node : nodes) {
52121
try {
53122
val.append(node.getValue());

src/main/java/com/hubspot/jinjava/tree/parse/TokenScannerSymbols.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,10 @@ public String getClosingComment() {
122122
}
123123
return closingComment;
124124
}
125+
126+
public static boolean isNoteTagOrExprChar(TokenScannerSymbols symbols, char c) {
127+
return (
128+
c == symbols.getNote() || c == symbols.getTag() || c == symbols.getExprStartChar()
129+
);
130+
}
125131
}

src/test/java/com/hubspot/jinjava/interpret/JinjavaInterpreterTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@
88
import com.google.common.collect.Lists;
99
import com.hubspot.jinjava.Jinjava;
1010
import com.hubspot.jinjava.JinjavaConfig;
11+
import com.hubspot.jinjava.features.FeatureConfig;
12+
import com.hubspot.jinjava.features.FeatureStrategies;
1113
import com.hubspot.jinjava.interpret.JinjavaInterpreter.InterpreterScopeClosable;
1214
import com.hubspot.jinjava.interpret.TemplateError.ErrorItem;
1315
import com.hubspot.jinjava.interpret.TemplateError.ErrorReason;
1416
import com.hubspot.jinjava.interpret.TemplateError.ErrorType;
17+
import com.hubspot.jinjava.mode.EagerExecutionMode;
1518
import com.hubspot.jinjava.mode.PreserveRawExecutionMode;
1619
import com.hubspot.jinjava.objects.date.FormattedDate;
1720
import com.hubspot.jinjava.objects.date.StrftimeFormatter;
1821
import com.hubspot.jinjava.tree.TextNode;
1922
import com.hubspot.jinjava.tree.output.BlockInfo;
23+
import com.hubspot.jinjava.tree.output.OutputList;
2024
import com.hubspot.jinjava.tree.parse.TextToken;
2125
import com.hubspot.jinjava.tree.parse.TokenScannerSymbols;
2226
import java.time.ZoneId;
@@ -503,4 +507,53 @@ public void itFiltersDuplicateErrors() {
503507

504508
assertThat(interpreter.getErrors()).containsExactly(error1, error2);
505509
}
510+
511+
@Test
512+
public void itPreventsAccidentalExpressions() {
513+
String makeExpression = "if (true) {\n{%- print deferred -%}\n}";
514+
String makeTag = "if (true) {\n{%- print '% print 123 %' -%}\n}";
515+
String makeNote = "if (true) {\n{%- print '# note #' -%}\n}";
516+
jinjava.getGlobalContext().put("deferred", DeferredValue.instance());
517+
518+
JinjavaInterpreter normalInterpreter = new JinjavaInterpreter(
519+
jinjava,
520+
jinjava.getGlobalContext(),
521+
JinjavaConfig.newBuilder().withExecutionMode(EagerExecutionMode.instance()).build()
522+
);
523+
JinjavaInterpreter preventingInterpreter = new JinjavaInterpreter(
524+
jinjava,
525+
jinjava.getGlobalContext(),
526+
JinjavaConfig
527+
.newBuilder()
528+
.withFeatureConfig(
529+
FeatureConfig
530+
.newBuilder()
531+
.add(OutputList.PREVENT_ACCIDENTAL_EXPRESSIONS, FeatureStrategies.ACTIVE)
532+
.build()
533+
)
534+
.withExecutionMode(EagerExecutionMode.instance())
535+
.build()
536+
);
537+
JinjavaInterpreter.pushCurrent(normalInterpreter);
538+
try {
539+
assertThat(normalInterpreter.render(makeExpression))
540+
.isEqualTo("if (true) {{% print deferred %}}");
541+
assertThat(normalInterpreter.render(makeTag))
542+
.isEqualTo("if (true) {% print 123 %}");
543+
assertThat(normalInterpreter.render(makeNote)).isEqualTo("if (true) {# note #}");
544+
} finally {
545+
JinjavaInterpreter.popCurrent();
546+
}
547+
JinjavaInterpreter.pushCurrent(preventingInterpreter);
548+
try {
549+
assertThat(preventingInterpreter.render(makeExpression))
550+
.isEqualTo("if (true) {\n" + "{#- #}{% print deferred %}}");
551+
assertThat(preventingInterpreter.render(makeTag))
552+
.isEqualTo("if (true) {\n" + "{#- #}% print 123 %}");
553+
assertThat(preventingInterpreter.render(makeNote))
554+
.isEqualTo("if (true) {\n" + "{#- #}# note #}");
555+
} finally {
556+
JinjavaInterpreter.popCurrent();
557+
}
558+
}
506559
}

0 commit comments

Comments
 (0)