From ba00ce8ccebe1f2e07aea7586ea6b635cfc11733 Mon Sep 17 00:00:00 2001 From: Brian Koehmstedt Date: Mon, 6 Feb 2023 20:18:49 -0800 Subject: [PATCH] If field validation error codes are unresolved and fallback to the defaultMessage on the error, this defaultMessage must be escaped due to the possibility of user input being in the error message. Fixes issue #323. --- .../plugin/formfields/FormFieldsTagLib.groovy | 17 ++++++- .../taglib/FieldTagWithBodySpec.groovy | 44 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy b/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy index 9e1cfdd8..20f8e260 100644 --- a/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy +++ b/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy @@ -33,6 +33,9 @@ import org.grails.scaffolding.model.property.DomainPropertyFactory import org.grails.web.servlet.mvc.GrailsWebRequest import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value +import org.springframework.context.MessageSource +import org.springframework.context.MessageSourceResolvable +import org.springframework.context.NoSuchMessageException import org.springframework.web.servlet.LocaleResolver import javax.servlet.http.HttpServletRequest @@ -68,6 +71,7 @@ class FormFieldsTagLib { DomainModelService domainModelService LocaleResolver localeResolver CodecLookup codecLookup + MessageSource messageSource static defaultEncodeAs = [taglib: 'raw'] @@ -458,7 +462,18 @@ class FormFieldsTagLib { value : (value instanceof Number || value instanceof Boolean || value) ? value : valueDefault, constraints : propertyAccessor.constraints, persistentProperty: propertyAccessor.domainProperty, - errors : propertyAccessor.errors.collect { message(error: it) }, + errors : propertyAccessor.errors.collect { error -> + String errorMsg = null + try { + errorMsg = error instanceof MessageSourceResolvable ? messageSource.getMessage(error, locale) : messageSource.getMessage(error.toString(), null, locale) + } + catch (NoSuchMessageException ignored) { + // no-op + } + // unresolved message codes fallback to the defaultMessage and this should + // be escaped as it could be an error message with user input + errorMsg && errorMsg == error.defaultMessage ? message(error: error, encodeAs: "HTML") : message(error: error) + }, required : attrs.containsKey("required") ? Boolean.valueOf(attrs.remove('required')) : propertyAccessor.required, invalid : attrs.containsKey("invalid") ? Boolean.valueOf(attrs.remove('invalid')) : propertyAccessor.invalid, prefix : resolvePrefix(attrs.remove('prefix')), diff --git a/src/test/groovy/grails/plugin/formfields/taglib/FieldTagWithBodySpec.groovy b/src/test/groovy/grails/plugin/formfields/taglib/FieldTagWithBodySpec.groovy index 40f072c4..f8ce2ce4 100644 --- a/src/test/groovy/grails/plugin/formfields/taglib/FieldTagWithBodySpec.groovy +++ b/src/test/groovy/grails/plugin/formfields/taglib/FieldTagWithBodySpec.groovy @@ -1,7 +1,11 @@ package grails.plugin.formfields.taglib +import grails.plugin.formfields.mock.Gender import grails.plugin.formfields.mock.Person import grails.testing.web.taglib.TagLibUnitTest +import grails.validation.ValidationErrors +import org.grails.plugins.web.taglib.FormatTagLib +import org.springframework.context.support.StaticMessageSource import spock.lang.Issue import grails.plugin.formfields.* @@ -56,4 +60,44 @@ class FieldTagWithBodySpec extends AbstractFormFieldsTagLibSpec implements TagLi expect: applyTemplate('${attrs.foo}', [personInstance: personInstance]) == 'bar' } + + @Issue("https://github.com/grails-fields-plugin/grails-fields/issues/323") + void 'validation defaultMessage strings are escaped'() { + given: + views['/_fields/default/_wrapper.gsp'] = '${widget}' + + and: + def person = new Person(name: 'Not Allowed', gender: Gender.Male, password: 'XYZ').with { + errors = new ValidationErrors(it) + errors.rejectValue('name', 'unresolved.code', 'custom error with special chars & < > \' "') + it + } + + when: + def result = applyTemplate('${errors[0]}', [personInstance: person]) + + then: + result == 'custom error with special chars & < > ' "' + } + + void 'resolved error codes are not escaped'() { + given: + views['/_fields/default/_wrapper.gsp'] = '${widget}' + + and: + ((StaticMessageSource) messageSource).addMessage('name.invalid', FormatTagLib.resolveLocale(null), '
Name is invalid
') + + and: + def person = new Person(name: 'Not Allowed', gender: Gender.Male, password: 'XYZ').with { + errors = new ValidationErrors(it) + errors.rejectValue('name', 'name.invalid', 'default error message') + it + } + + when: + def result = applyTemplate('${errors[0]}', [personInstance: person]) + + then: + result == '
Name is invalid
' + } } \ No newline at end of file