Skip to content

Commit

Permalink
#254 attribute names with a template expression are not evaluated as …
Browse files Browse the repository at this point in the history
…smart attributes
  • Loading branch information
casid committed Aug 2, 2023
1 parent 3e4c084 commit 66dbec0
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ public void onHtmlTagBodyCodePart(int depth, String codePart, String tagName) {
@Override
public void onHtmlTagAttributeCodePart(int depth, String codePart, String tagName, String attributeName) {
writeIndentation(depth);
kotlinCode.append("jteOutput.setContext(\"").append(tagName).append("\", \"").append(attributeName).append("\")\n");
kotlinCode.append("jteOutput.setContext(\"").append(tagName).append("\", \"").appendEscaped(attributeName).append("\")\n");

writeCodePart(depth, codePart);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import gg.jte.TemplateEngine;
import gg.jte.TemplateException;
import gg.jte.html.OwaspHtmlPolicy;
import gg.jte.html.policy.PreventInlineEventHandlers;
import gg.jte.html.policy.PreventSingleQuotedAttributes;
import gg.jte.html.policy.*;
import gg.jte.output.StringOutput;
import gg.jte.runtime.TemplateUtils;
import gg.jte.support.LocalizationSupport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Collections;
Expand Down Expand Up @@ -1409,6 +1410,51 @@ void localization_contentParams2() {
assertThat(output.toString()).isEqualTo("<span>Hello? <a style=\"color: foo;\">Click here</a></span>");
}

@Nested
class DynamicAttributesForHotwire {

class HotwireHtmlPolicy extends PolicyGroup {
HotwireHtmlPolicy() {
addPolicy(new PreventUppercaseTagsAndAttributes());
addPolicy(new PreventOutputInTagsAndAttributes(false));
addPolicy(new PreventUnquotedAttributes());
}
}

@BeforeEach
void setUp() {
templateEngine.setHtmlPolicy(new HotwireHtmlPolicy());
}

@Test
void attributes_dynamicNameForHotwire() {
codeResolver.givenCode("template.kte", "@param controller:String\n@param target:String\n<div data-controller=\"hello\">\n<input data-${controller}-target=\"${target}\"/></div>");

templateEngine.render("template.kte", TemplateUtils.toMap("controller", "hello", "target", "name"), output);

assertThat(output.toString()).isEqualTo("<div data-controller=\"hello\">\n<input data-hello-target=\"name\"/></div>");
}

@Test
void attributes_dynamicNameForHotwire_unsafe() {
codeResolver.givenCode("template.kte", "@param controller:String\n@param target:String\n<div data-controller=\"hello\">\n<input data-$unsafe{controller}-target=\"${target}\"/></div>");

templateEngine.render("template.kte", TemplateUtils.toMap("controller", "hello", "target", "name"), output);

assertThat(output.toString()).isEqualTo("<div data-controller=\"hello\">\n<input data-hello-target=\"name\"/></div>");
}

@Test
void attributes_dynamicNameForHotwire_unsafe_worksWithDefaultPolicyToo() {
templateEngine.setHtmlPolicy(new OwaspHtmlPolicy());
codeResolver.givenCode("template.kte", "@param controller:String\n@param target:String\n<div data-controller=\"hello\">\n<input data-$unsafe{controller}-target=\"${target}\"/></div>");

templateEngine.render("template.kte", TemplateUtils.toMap("controller", "hello", "target", "name"), output);

assertThat(output.toString()).isEqualTo("<div data-controller=\"hello\">\n<input data-hello-target=\"name\"/></div>");
}
}

@SuppressWarnings("unused")
public static class MyLocalizer implements LocalizationSupport {
Map<String, Object> resources = TemplateUtils.toMap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@

public class PreventOutputInTagsAndAttributes implements HtmlPolicy {

private final boolean preventExpressionsInAttributes;

public PreventOutputInTagsAndAttributes() {
this(true);
}

public PreventOutputInTagsAndAttributes(boolean preventExpressionsInAttributes) {
this.preventExpressionsInAttributes = preventExpressionsInAttributes;
}

@Override
public void validateHtmlTag(HtmlTag htmlTag) throws HtmlPolicyException {
if ( htmlTag.getName().contains("${") ) {
Expand All @@ -16,7 +26,7 @@ public void validateHtmlTag(HtmlTag htmlTag) throws HtmlPolicyException {

@Override
public void validateHtmlAttribute(HtmlTag htmlTag, HtmlAttribute htmlAttribute) throws HtmlPolicyException {
if ( htmlAttribute.getName().contains("${") ) {
if ( preventExpressionsInAttributes && htmlAttribute.getName().contains("${") ) {
throw new HtmlPolicyException("Illegal HTML attribute name " + htmlAttribute.getName() + "! Expressions in HTML attribute names are not allowed.");
}

Expand Down
8 changes: 6 additions & 2 deletions jte/src/main/java/gg/jte/compiler/TemplateParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ private void interceptHtmlTags() {
} else if (currentAttribute.quoteCount == 2) {
currentAttribute.value = templateCode.substring(currentAttribute.valueStartIndex, i);

if (currentAttribute.containsSingleOutput) {
if (currentAttribute.isSmartAttribute()) {
extractTextPart(getLastWhitespaceIndex(currentAttribute.startIndex - 1), Mode.Code);
lastIndex = i + 1;

Expand Down Expand Up @@ -760,7 +760,7 @@ private void interceptHtmlTags() {

currentHtmlTag.attributes.add(attribute);

if (attribute.containsSingleOutput) {
if (attribute.isSmartAttribute()) {
outputPrevented = true;
}

Expand Down Expand Up @@ -1229,5 +1229,9 @@ public char getQuotes() {
public boolean isBoolean() {
return bool;
}

public boolean isSmartAttribute() {
return containsSingleOutput && !name.contains("${") && !name.contains("$unsafe{");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import gg.jte.html.HtmlTemplateOutput;
import gg.jte.html.OwaspHtmlPolicy;
import gg.jte.html.policy.PreventInlineEventHandlers;
import gg.jte.html.policy.PreventSingleQuotedAttributes;
import gg.jte.html.policy.*;
import gg.jte.output.StringOutput;
import gg.jte.runtime.TemplateUtils;
import gg.jte.support.LocalizationSupport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Collections;
Expand Down Expand Up @@ -1612,6 +1613,51 @@ void localization_contentParams2() {
assertThat(output.toString()).isEqualTo("<span>Hello? <a style=\"color: foo;\">Click here</a></span>");
}

@Nested
class DynamicAttributesForHotwire {

class HotwireHtmlPolicy extends PolicyGroup {
HotwireHtmlPolicy() {
addPolicy(new PreventUppercaseTagsAndAttributes());
addPolicy(new PreventOutputInTagsAndAttributes(false));
addPolicy(new PreventUnquotedAttributes());
}
}

@BeforeEach
void setUp() {
templateEngine.setHtmlPolicy(new HotwireHtmlPolicy());
}

@Test
void attributes_dynamicNameForHotwire() {
codeResolver.givenCode("template.jte", "@param String controller\n@param String target\n<div data-controller=\"hello\">\n<input data-${controller}-target=\"${target}\"/></div>");

templateEngine.render("template.jte", TemplateUtils.toMap("controller", "hello", "target", "name"), output);

assertThat(output.toString()).isEqualTo("<div data-controller=\"hello\">\n<input data-hello-target=\"name\"/></div>");
}

@Test
void attributes_dynamicNameForHotwire_unsafe() {
codeResolver.givenCode("template.jte", "@param String controller\n@param String target\n<div data-controller=\"hello\">\n<input data-$unsafe{controller}-target=\"${target}\"/></div>");

templateEngine.render("template.jte", TemplateUtils.toMap("controller", "hello", "target", "name"), output);

assertThat(output.toString()).isEqualTo("<div data-controller=\"hello\">\n<input data-hello-target=\"name\"/></div>");
}

@Test
void attributes_dynamicNameForHotwire_unsafe_worksWithDefaultPolicyToo() {
templateEngine.setHtmlPolicy(new OwaspHtmlPolicy());
codeResolver.givenCode("template.jte", "@param String controller\n@param String target\n<div data-controller=\"hello\">\n<input data-$unsafe{controller}-target=\"${target}\"/></div>");

templateEngine.render("template.jte", TemplateUtils.toMap("controller", "hello", "target", "name"), output);

assertThat(output.toString()).isEqualTo("<div data-controller=\"hello\">\n<input data-hello-target=\"name\"/></div>");
}
}

@SuppressWarnings("unused")
public static class MyLocalizer implements LocalizationSupport {
Map<String, Object> resources = TemplateUtils.toMap(
Expand Down

0 comments on commit 66dbec0

Please sign in to comment.