Skip to content

Commit 7f0f3af

Browse files
committed
Fix for GRAILS-9247 and GRAILS-9392 g:fieldValue encoding issues
1 parent 8be41dc commit 7f0f3af

File tree

7 files changed

+80
-14
lines changed

7 files changed

+80
-14
lines changed

grails-plugin-codecs/src/main/groovy/org/codehaus/groovy/grails/plugins/codecs/HTMLCodec.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
package org.codehaus.groovy.grails.plugins.codecs;
1616

17+
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes;
1718
import org.codehaus.groovy.grails.web.util.StreamCharBuffer;
1819
import org.springframework.web.context.request.RequestAttributes;
1920
import org.springframework.web.context.request.RequestContextHolder;
@@ -40,7 +41,7 @@ public static CharSequence encode(Object target) {
4041
public static boolean shouldEncode() {
4142
final RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
4243
if (attributes != null) {
43-
Object codecName = attributes.getAttribute("org.codehaus.groovy.grails.GSP_CODEC",
44+
Object codecName = attributes.getAttribute(GrailsApplicationAttributes.GSP_CODEC,
4445
RequestAttributes.SCOPE_REQUEST);
4546
if (codecName != null && codecName.toString().equalsIgnoreCase("html")) {
4647
return false;

grails-plugin-gsp/src/main/groovy/org/codehaus/groovy/grails/plugins/web/taglib/ValidationTagLib.groovy

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import java.text.DecimalFormat
2323
import java.text.DecimalFormatSymbols
2424
import org.apache.commons.lang.StringEscapeUtils
2525
import org.codehaus.groovy.grails.plugins.codecs.HTMLCodec
26+
import org.codehaus.groovy.grails.web.taglib.GroovyPageAttributes;
2627
import org.springframework.beans.PropertyEditorRegistry
2728
import org.springframework.context.MessageSourceResolvable
2829
import org.springframework.context.NoSuchMessageException
@@ -76,6 +77,9 @@ class ValidationTagLib {
7677
Closure fieldValue = { attrs, body ->
7778
def bean = attrs.bean
7879
def field = attrs.field?.toString()
80+
81+
def tagSyntaxCall = (attrs instanceof GroovyPageAttributes) ? attrs.isGspTagSyntaxCall() : false
82+
7983
if (bean && field) {
8084
if (bean.metaClass.hasProperty(bean,'errors')) {
8185
Errors errors = bean.errors
@@ -87,7 +91,7 @@ class ValidationTagLib {
8791
}
8892
}
8993
if (rejectedValue != null) {
90-
out << formatValue(rejectedValue, field)
94+
out << formatValue(rejectedValue, field, tagSyntaxCall)
9195
}
9296
}
9397
else {
@@ -96,7 +100,7 @@ class ValidationTagLib {
96100
rejectedValue = rejectedValue?."$fieldPart"
97101
}
98102
if (rejectedValue != null) {
99-
out << formatValue(rejectedValue, field)
103+
out << formatValue(rejectedValue, field, tagSyntaxCall)
100104
}
101105
}
102106
}
@@ -422,12 +426,12 @@ class ValidationTagLib {
422426
* formatted according to the current user's locale during the
423427
* conversion to a string.
424428
*/
425-
def formatValue(value, String propertyPath = null) {
429+
def formatValue(value, String propertyPath = null, Boolean tagSyntaxCall = false) {
426430
PropertyEditorRegistry registry = RequestContextHolder.currentRequestAttributes().getPropertyEditorRegistry()
427431
PropertyEditor editor = registry.findCustomEditor(value.getClass(), propertyPath)
428432
if (editor) {
429433
editor.setValue(value)
430-
return HTMLCodec.shouldEncode() && !(value instanceof Number) ? editor.asText?.encodeAsHTML() : editor.asText
434+
return (tagSyntaxCall || HTMLCodec.shouldEncode()) && !(value instanceof Number) ? editor.asText?.encodeAsHTML() : editor.asText
431435
}
432436

433437
if (value instanceof Number) {
@@ -445,6 +449,6 @@ class ValidationTagLib {
445449
value = message(message: value)
446450
}
447451

448-
return HTMLCodec.shouldEncode() ? value.toString().encodeAsHTML() : value
452+
return (tagSyntaxCall || HTMLCodec.shouldEncode()) ? value.toString().encodeAsHTML() : value
449453
}
450454
}

grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/taglib/ValidationTagLibTests.groovy

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.codehaus.groovy.grails.web.taglib
22

3+
import org.codehaus.groovy.grails.support.MockStringResourceLoader
34
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes
45
import org.springframework.beans.factory.support.RootBeanDefinition
56
import org.springframework.context.MessageSourceResolvable
@@ -118,14 +119,49 @@ enum Title implements org.springframework.context.MessageSourceResolvable {
118119
b.properties = [title:"<script>alert('escape me')</script>"]
119120

120121
def template = '''<g:fieldValue bean="${book}" field="title" />'''
122+
def htmlCodecDirective = '<%@page defaultCodec="HTML" %>'
123+
def expected = "&lt;script&gt;alert(&#39;escape me&#39;)&lt;/script&gt;"
124+
assertOutputEquals(expected, template, [book:b])
125+
assertOutputEquals(expected, htmlCodecDirective + template, [book:b])
126+
}
127+
128+
void testFieldValueHtmlEscapingWithFunctionSyntaxCall() {
129+
def b = ga.getDomainClass("ValidationTagLibBook").newInstance()
130+
b.properties = [title:"<script>alert('escape me')</script>"]
121131

122-
assertOutputEquals("&lt;script&gt;alert(&#39;escape me&#39;)&lt;/script&gt;", template, [book:b])
132+
def template = '''${fieldValue(bean:book, field:"title")}'''
133+
def htmlCodecDirective = '<%@page defaultCodec="HTML" %>'
134+
def expected = "&lt;script&gt;alert(&#39;escape me&#39;)&lt;/script&gt;"
135+
assertOutputEquals(expected, template, [book:b])
136+
assertOutputEquals(expected, htmlCodecDirective + template, [book:b])
137+
}
123138

124-
request.setAttribute("org.codehaus.groovy.grails.GSP_CODEC", 'html')
139+
void testFieldValueHtmlEscapingDifferentEncodings() {
140+
def b = ga.getDomainClass("ValidationTagLibBook").newInstance()
141+
b.properties = [title:"<script>alert('escape me')</script>"]
125142

126-
assertOutputEquals("<script>alert('escape me')</script>", template, [book:b])
143+
def template = '''${fieldValue(bean:book, field:"title")}'''
144+
def htmlCodecDirective = '<%@page defaultCodec="HTML" %>'
145+
def expected = "&lt;script&gt;alert(&#39;escape me&#39;)&lt;/script&gt;"
146+
147+
def resourceLoader = new MockStringResourceLoader()
148+
resourceLoader.registerMockResource('/_sometemplate.gsp', htmlCodecDirective + template)
149+
resourceLoader.registerMockResource('/_sometemplate_nocodec.gsp', template)
150+
appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
151+
152+
assertOutputEquals(expected, '<g:render template="/sometemplate" model="[book:book]" />', [book:b])
153+
assertOutputEquals(expected + expected, template + '<g:render template="/sometemplate" model="[book:book]" />', [book:b])
154+
assertOutputEquals(expected + expected, htmlCodecDirective + template + '<g:render template="/sometemplate" model="[book:book]" />', [book:b])
155+
assertOutputEquals(expected + expected, '<g:render template="/sometemplate" model="[book:book]" />' + template, [book:b])
156+
assertOutputEquals(expected + expected, htmlCodecDirective + '<g:render template="/sometemplate" model="[book:book]" />' + template, [book:b])
157+
158+
assertOutputEquals(expected, '<g:render template="/sometemplate_nocodec" model="[book:book]" />', [book:b])
159+
assertOutputEquals(expected + expected, template + '<g:render template="/sometemplate_nocodec" model="[book:book]" />', [book:b])
160+
assertOutputEquals(expected + expected, htmlCodecDirective + template + '<g:render template="/sometemplate_nocodec" model="[book:book]" />', [book:b])
161+
assertOutputEquals(expected + expected, '<g:render template="/sometemplate_nocodec" model="[book:book]" />' + template, [book:b])
162+
assertOutputEquals(expected + expected, htmlCodecDirective + '<g:render template="/sometemplate_nocodec" model="[book:book]" />' + template, [book:b])
127163
}
128-
164+
129165
void testFieldValueTag() {
130166
def b = ga.getDomainClass("ValidationTagLibBook").newInstance()
131167
b.properties = [publisherURL:"a_bad_url"]

grails-web/src/main/groovy/org/codehaus/groovy/grails/web/pages/GroovyPage.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ public final void invokeTag(String tagName, String tagNamespace, int lineNumber,
381381
if (!(attrs instanceof GroovyPageAttributes)) {
382382
attrs = new GroovyPageAttributes(attrs);
383383
}
384+
((GroovyPageAttributes)attrs).setGspTagSyntaxCall(true);
384385
switch (tag.getParameterTypes().length) {
385386
case 1:
386387
tagresult = tag.call(new Object[]{attrs});
@@ -473,8 +474,9 @@ public final static Object captureTagOutput(TagLibraryLookup gspTagLibraryLookup
473474
String tagName, Map attrs, Object body, GrailsWebRequest webRequest) {
474475

475476
if (!(attrs instanceof GroovyPageAttributes)) {
476-
attrs = new GroovyPageAttributes(attrs);
477+
attrs = new GroovyPageAttributes(attrs, false);
477478
}
479+
((GroovyPageAttributes)attrs).setGspTagSyntaxCall(false);
478480
GroovyObject tagLib = lookupCachedTagLib(gspTagLibraryLookup, namespace, tagName);
479481

480482
if (tagLib == null) {

grails-web/src/main/groovy/org/codehaus/groovy/grails/web/pages/GroovyPageWritable.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,21 @@ public Writer writeTo(Writer out) throws IOException {
157157
}
158158

159159
GroovyPageBinding binding = createBinding(parentBinding);
160+
String previousGspCode = null;
160161
if (hasRequest) {
161162
request.setAttribute(GrailsApplicationAttributes.PAGE_SCOPE, binding);
163+
previousGspCode = (String)request.getAttribute(GrailsApplicationAttributes.GSP_CODEC);
162164
}
165+
163166
if (metaInfo.getCodecClass() != null) {
164167
if (hasRequest) {
165-
request.setAttribute("org.codehaus.groovy.grails.GSP_CODEC", metaInfo.getCodecName());
168+
request.setAttribute(GrailsApplicationAttributes.GSP_CODEC, metaInfo.getCodecName());
166169
}
167170
binding.setVariableDirectly(GroovyPage.CODEC_VARNAME, metaInfo.getCodecClass());
168171
} else {
172+
if (hasRequest) {
173+
request.setAttribute(GrailsApplicationAttributes.GSP_CODEC, null);
174+
}
169175
binding.setVariableDirectly(GroovyPage.CODEC_VARNAME, gspNoneCodeInstance);
170176
}
171177
binding.setVariableDirectly(GroovyPage.RESPONSE, response);
@@ -213,6 +219,7 @@ public Writer writeTo(Writer out) throws IOException {
213219
} else {
214220
request.setAttribute(GrailsApplicationAttributes.PAGE_SCOPE, parentBinding);
215221
}
222+
request.setAttribute(GrailsApplicationAttributes.GSP_CODEC, previousGspCode);
216223
}
217224
}
218225
if (debugTemplates) {

grails-web/src/main/groovy/org/codehaus/groovy/grails/web/servlet/GrailsApplicationAttributes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public interface GrailsApplicationAttributes extends ApplicationAttributes {
5151
String TAG_CACHE = "org.codehaus.groovy.grails.TAG_CACHE";
5252
String ID_PARAM = "id";
5353
String GSP_TO_RENDER = "org.codehaus.groovy.grails.GSP_TO_RENDER";
54+
String GSP_CODEC = "org.codehaus.groovy.grails.GSP_CODEC";
5455
String WEB_REQUEST = "org.codehaus.groovy.grails.WEB_REQUEST";
5556
String PAGE_SCOPE = "org.codehaus.groovy.grails.PAGE_SCOPE";
5657
String GSP_TMP_WRITER = "org.codehaus.groovy.grails.GSP_TMP_WRITER";

grails-web/src/main/groovy/org/codehaus/groovy/grails/web/taglib/GroovyPageAttributes.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,29 @@
3030
*/
3131
@SuppressWarnings({ "unchecked", "rawtypes" })
3232
public class GroovyPageAttributes extends TypeConvertingMap {
33+
boolean gspTagSyntaxCall=true;
34+
3335
public GroovyPageAttributes() {
3436
super();
3537
}
36-
38+
3739
public GroovyPageAttributes(Map map) {
38-
super(map);
40+
this(map, true);
3941
}
4042

43+
public GroovyPageAttributes(Map map, boolean gspTagSyntaxCall) {
44+
super(map);
45+
this.gspTagSyntaxCall=gspTagSyntaxCall;
46+
}
47+
48+
public boolean isGspTagSyntaxCall() {
49+
return gspTagSyntaxCall;
50+
}
51+
52+
public void setGspTagSyntaxCall(boolean gspTagSyntaxCall) {
53+
this.gspTagSyntaxCall=gspTagSyntaxCall;
54+
}
55+
4156
@Override
4257
public Object clone() {
4358
return new GroovyPageAttributes(new LinkedHashMap(wrappedMap));

0 commit comments

Comments
 (0)