Skip to content

Commit bf66df2

Browse files
authored
Merge pull request #1128 from HubSpot/capped-rendering
Introduces a limited length render filter and HTML tag close filter
2 parents b60c7e8 + 10b4ff6 commit bf66df2

File tree

7 files changed

+207
-7
lines changed

7 files changed

+207
-7
lines changed

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import com.hubspot.jinjava.tree.output.OutputNode;
5151
import com.hubspot.jinjava.tree.output.RenderedOutputNode;
5252
import com.hubspot.jinjava.util.EagerReconstructionUtils;
53+
import com.hubspot.jinjava.util.RenderLimitUtils;
5354
import com.hubspot.jinjava.util.Variable;
5455
import com.hubspot.jinjava.util.WhitespaceUtils;
5556
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -259,7 +260,11 @@ public String renderFlat(String template) {
259260
* @return rendered result
260261
*/
261262
public String render(String template) {
262-
return render(parse(template), true);
263+
return render(template, config.getMaxOutputSize());
264+
}
265+
266+
public String render(String template, long renderLimit) {
267+
return render(parse(template), true, renderLimit);
263268
}
264269

265270
/**
@@ -270,7 +275,19 @@ public String render(String template) {
270275
* @return rendered result
271276
*/
272277
public String render(Node root) {
273-
return render(root, true);
278+
return render(root, true, config.getMaxOutputSize());
279+
}
280+
281+
/**
282+
* Render the given root node with an option to process extend parents.
283+
* Equivalent to render(root, processExtendRoots).
284+
* @param root
285+
* node to render
286+
* @param processExtendRoots
287+
* @return
288+
*/
289+
public String render(Node root, boolean processExtendRoots) {
290+
return render(root, processExtendRoots, config.getMaxOutputSize());
274291
}
275292

276293
/**
@@ -280,11 +297,14 @@ public String render(Node root) {
280297
* node to render
281298
* @param processExtendRoots
282299
* if true, also render all extend parents
300+
* @param renderLimit
301+
* the number of characters the result may contain
283302
* @return rendered result
284303
*/
285-
public String render(Node root, boolean processExtendRoots) {
286-
OutputList output = new OutputList(config.getMaxOutputSize());
287-
304+
private String render(Node root, boolean processExtendRoots, long renderLimit) {
305+
OutputList output = new OutputList(
306+
RenderLimitUtils.clampProvidedRenderLimitToConfig(renderLimit, config)
307+
);
288308
for (Node node : root.getChildren()) {
289309
lineNumber = node.getLineNumber();
290310
position = node.getStartPosition();
@@ -340,8 +360,8 @@ public String render(Node root, boolean processExtendRoots) {
340360
return output.getValue();
341361
}
342362
}
343-
StringBuilder ignoredOutput = new StringBuilder();
344363

364+
StringBuilder ignoredOutput = new StringBuilder();
345365
// render all extend parents, keeping the last as the root output
346366
if (processExtendRoots) {
347367
Set<String> extendPaths = new HashSet<>();
@@ -406,6 +426,7 @@ public String render(Node root, boolean processExtendRoots) {
406426
}
407427

408428
resolveBlockStubs(output);
429+
409430
if (ignoredOutput.length() > 0) {
410431
return (
411432
EagerReconstructionUtils.labelWithNotes(
@@ -421,7 +442,6 @@ public String render(Node root, boolean processExtendRoots) {
421442
output.getValue()
422443
);
423444
}
424-
425445
return output.getValue();
426446
}
427447

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.hubspot.jinjava.lib.filter;
2+
3+
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
4+
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
5+
import com.hubspot.jinjava.doc.annotations.JinjavaSnippet;
6+
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
7+
import java.util.Objects;
8+
import org.jsoup.Jsoup;
9+
10+
@JinjavaDoc(
11+
value = "Closes open HTML tags in a string",
12+
input = @JinjavaParam(value = "s", desc = "String to close", required = true),
13+
snippets = { @JinjavaSnippet(code = "{{ \"<p> Hello, world\"|closehtml }}") }
14+
)
15+
public class CloseHtmlFilter implements Filter {
16+
17+
@Override
18+
public String getName() {
19+
return "closehtml";
20+
}
21+
22+
@Override
23+
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
24+
return Jsoup.parseBodyFragment(Objects.toString(var)).body().html();
25+
}
26+
}

src/main/java/com/hubspot/jinjava/lib/filter/RenderFilter.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.hubspot.jinjava.lib.filter;
22

3+
import com.hubspot.jinjava.JinjavaConfig;
34
import com.hubspot.jinjava.doc.annotations.JinjavaDoc;
45
import com.hubspot.jinjava.doc.annotations.JinjavaParam;
56
import com.hubspot.jinjava.doc.annotations.JinjavaSnippet;
67
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
78
import java.util.Objects;
9+
import org.apache.commons.lang3.math.NumberUtils;
810

911
@JinjavaDoc(
1012
value = "Renders a template string early to be used by other filters and functions",
@@ -24,6 +26,16 @@ public String getName() {
2426

2527
@Override
2628
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
29+
if (args.length > 0) {
30+
String firstArg = args[0];
31+
return interpreter.render(
32+
Objects.toString(var),
33+
NumberUtils.toLong(
34+
firstArg,
35+
JinjavaConfig.newBuilder().build().getMaxOutputSize()
36+
)
37+
);
38+
}
2739
return interpreter.render(Objects.toString(var));
2840
}
2941
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.hubspot.jinjava.util;
2+
3+
import com.hubspot.jinjava.JinjavaConfig;
4+
5+
public class RenderLimitUtils {
6+
7+
public static long clampProvidedRenderLimitToConfig(
8+
long providedLimit,
9+
JinjavaConfig jinjavaConfig
10+
) {
11+
long configMaxOutput = jinjavaConfig.getMaxOutputSize();
12+
13+
if (configMaxOutput <= 0) {
14+
return providedLimit;
15+
}
16+
17+
if (providedLimit <= 0) {
18+
return configMaxOutput;
19+
}
20+
21+
return Math.min(providedLimit, configMaxOutput);
22+
}
23+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.hubspot.jinjava.lib.filter;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.hubspot.jinjava.BaseInterpretingTest;
6+
import org.junit.Before;
7+
import org.junit.Test;
8+
9+
public class CloseHtmlFilterTest extends BaseInterpretingTest {
10+
CloseHtmlFilter f;
11+
12+
@Before
13+
public void setup() {
14+
f = new CloseHtmlFilter();
15+
}
16+
17+
@Test
18+
public void itClosesTags() {
19+
String openTags = "<p>Hello, world!";
20+
assertThat(f.filter(openTags, interpreter)).isEqualTo("<p>Hello, world!</p>");
21+
}
22+
23+
@Test
24+
public void itIgnoresClosedTags() {
25+
String openTags = "<p>Hello, world!</p>";
26+
assertThat(f.filter(openTags, interpreter)).isEqualTo("<p>Hello, world!</p>");
27+
}
28+
29+
@Test
30+
public void itClosesMultipleTags() {
31+
String openTags = "<h1><p>Hello, world!";
32+
assertThat(f.filter(openTags, interpreter))
33+
.isEqualTo("<h1><p>Hello, world!</p></h1>");
34+
}
35+
}

src/test/java/com/hubspot/jinjava/lib/filter/RenderFilterTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,42 @@ public void itRendersObject() {
2020

2121
assertThat(filter.filter(stringToRender, interpreter)).isEqualTo("world");
2222
}
23+
24+
@Test
25+
public void itRendersObjectWithinLimit() {
26+
String stringToRender = "{% if null %}Hello{% else %}world{% endif %}";
27+
28+
assertThat(filter.filter(stringToRender, interpreter, "5")).isEqualTo("world");
29+
}
30+
31+
@Test
32+
public void itDoesNotRenderObjectOverLimit() {
33+
String stringToRender = "{% if null %}Hello{% else %}world{% endif %}";
34+
35+
assertThat(filter.filter(stringToRender, interpreter, "4")).isEqualTo("");
36+
}
37+
38+
@Test
39+
public void itRendersPartialObjectOverLimit() {
40+
String stringToRender = "Hello{% if null %}Hello{% else %}world{% endif %}";
41+
42+
assertThat(filter.filter(stringToRender, interpreter, "7")).isEqualTo("Hello");
43+
}
44+
45+
@Test
46+
public void itCountsHtmlTags() {
47+
String stringToRender = "<p>Hello</p>{% if null %}Hello{% else %}world{% endif %}";
48+
49+
assertThat(filter.filter(stringToRender, interpreter, "15"))
50+
.isEqualTo("<p>Hello</p>");
51+
}
52+
53+
@Test
54+
public void itDoesNotAlwaysCompleteHtmlTags() {
55+
String stringToRender =
56+
"<p> Hello, {% if null %}world{% else %}world!{% endif %} </p>";
57+
58+
assertThat(filter.filter(stringToRender, interpreter, "17"))
59+
.isEqualTo("<p> Hello, world!");
60+
}
2361
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.hubspot.jinjava.util;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.hubspot.jinjava.JinjavaConfig;
6+
import org.junit.Test;
7+
8+
public class RenderLimitUtilsTest {
9+
10+
@Test
11+
public void itPicksLowerLimitWhenConfigIsSet() {
12+
assertThat(
13+
RenderLimitUtils.clampProvidedRenderLimitToConfig(100, configWithOutputSize(10))
14+
)
15+
.isEqualTo(10);
16+
}
17+
18+
@Test
19+
public void itKeepsConfigLimitWhenConfigSetAndUnlimitedProvided() {
20+
assertThat(
21+
RenderLimitUtils.clampProvidedRenderLimitToConfig(0, configWithOutputSize(10))
22+
)
23+
.isEqualTo(10);
24+
assertThat(
25+
RenderLimitUtils.clampProvidedRenderLimitToConfig(-10, configWithOutputSize(10))
26+
)
27+
.isEqualTo(10);
28+
}
29+
30+
@Test
31+
public void itUsesProvidedLimitWhenConfigIsUnlimited() {
32+
assertThat(
33+
RenderLimitUtils.clampProvidedRenderLimitToConfig(10, configWithOutputSize(0))
34+
)
35+
.isEqualTo(10);
36+
37+
assertThat(
38+
RenderLimitUtils.clampProvidedRenderLimitToConfig(10, configWithOutputSize(-10))
39+
)
40+
.isEqualTo(10);
41+
}
42+
43+
private JinjavaConfig configWithOutputSize(long size) {
44+
return JinjavaConfig.newBuilder().withMaxOutputSize(size).build();
45+
}
46+
}

0 commit comments

Comments
 (0)