From f6df7311adf668c343da2bb8de4c9168bd2c6f3b Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Thu, 5 Sep 2019 17:35:00 +0200
Subject: [PATCH] fields: add validation on comment field (#30618)

---
 tests/test_admin_pages.py | 74 +++++++++++++++++++++++++++++++++++++++
 wcs/fields.py             |  2 ++
 wcs/qommon/form.py        | 11 ++++--
 3 files changed, 84 insertions(+), 3 deletions(-)

diff --git a/tests/test_admin_pages.py b/tests/test_admin_pages.py
index 3e7ca3de..3ff3dc7f 100644
--- a/tests/test_admin_pages.py
+++ b/tests/test_admin_pages.py
@@ -1729,16 +1729,90 @@ def test_form_edit_comment_field(pub):
     resp = app.get('/backoffice/forms/1/fields/')
     resp.forms[0]['label'] = '<div>blah</div>'
     resp.forms[0]['type'] = 'Comment'
     resp = resp.forms[0].submit()
     assert resp.location == 'http://example.net/backoffice/forms/1/fields/'
     assert FormDef.get(formdef.id).fields[-1].label == '<div>blah</div>'
 
 
+def test_form_comment_field_textwidget_validation(pub):
+    create_superuser(pub)
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'form title'
+    # legacy, ezt syntax in a non-html field will be presented as a textarea
+    formdef.fields = [fields.CommentField(id='1', type='comment',
+        label='[if-any toto]hello world[end]')]
+    formdef.store()
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/forms/1/fields/1/')
+
+    # bad {% %} Django template syntax
+    assert 'WysiwygTextWidget' not in resp.text
+    resp.form.fields['label'][0].value = '{% if cond %}no endif provided'
+    resp = resp.form.submit('submit')
+    assert 'syntax error in Django template: Unclosed tag on line 1' in resp.text
+
+    # bad {{ }} Django template syntax
+    assert 'WysiwygTextWidget' not in resp.text
+    resp.form.fields['label'][0].value = '{{0+0}}'
+    resp = resp.form.submit('submit')
+    assert 'syntax error in Django template: Could not parse' in resp.text
+
+    # bad EZT syntax
+    assert 'WysiwygTextWidget' not in resp.text
+    resp.form.fields['label'][0].value = '[end]'
+    resp = resp.form.submit('submit')
+    assert 'syntax error in ezt template: unmatched [end]' in resp.text
+
+    # good syntax
+    assert 'WysiwygTextWidget' not in resp.text
+    resp.form.fields['label'][0].value = '{{variable}}'
+    resp = resp.form.submit('submit')
+    assert FormDef.get(formdef.id).fields[0].label == '{{variable}}'
+
+def test_form_comment_field_wysiwygtextwidget_validation(pub):
+    create_superuser(pub)
+    create_role()
+
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'form title'
+    formdef.fields = [fields.CommentField(id='1', label='a comment field', type='comment')]
+    formdef.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/forms/1/fields/1/')
+    assert 'a comment field' in resp.text
+
+    # bad {% %} Django template syntax
+    assert 'WysiwygTextWidget' in resp.text
+    resp.form.fields['label'][0].value = '{% if cond %}no endif provided'
+    resp = resp.form.submit('submit')
+    assert 'syntax error in Django template: Unclosed tag on line 1' in resp.text
+
+    # bad {{ }} Django template syntax
+    assert 'WysiwygTextWidget' in resp.text
+    resp.form.fields['label'][0].value = '{{0+0}}'
+    resp = resp.form.submit('submit')
+    assert 'syntax error in Django template: Could not parse' in resp.text
+
+    # bad EZT syntax
+    assert 'WysiwygTextWidget' in resp.text
+    resp.form.fields['label'][0].value = '[end]'
+    resp = resp.form.submit('submit')
+    assert 'syntax error in ezt template: unmatched [end]' in resp.text
+
+    # good syntax
+    assert 'WysiwygTextWidget' in resp.text
+    resp.form.fields['label'][0].value = '{{variable}}'
+    resp = resp.form.submit('submit')
+    assert FormDef.get(formdef.id).fields[0].label == '{{variable}}'
+
 def test_form_edit_map_field(pub):
     create_superuser(pub)
     create_role()
 
     FormDef.wipe()
     formdef = FormDef()
     formdef.name = 'form title'
     formdef.fields = [fields.MapField(id='1', label='a field', type='map')]
diff --git a/wcs/fields.py b/wcs/fields.py
index 750921f7..22b29825 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -714,19 +714,21 @@ class CommentField(Field):
 
     def get_display_locations_options(self):
         return [('validation', _('Validation Page')),
                 ('summary', _('Summary Page'))]
 
     def fill_admin_form(self, form):
         if self.label and (self.label[0] != '<' and '[end]' in self.label):
             form.add(TextWidget, 'label', title=_('Label'), value=self.label,
+                    validation_function=ComputedExpressionWidget.validate_template,
                     required=True, cols=70, rows=3, render_br=False)
         else:
             form.add(WysiwygTextWidget, 'label', title=_('Label'),
+                    validation_function=ComputedExpressionWidget.validate_template,
                     value=self.get_html_content(), required=True)
         form.add(StringWidget, 'extra_css_class', title = _('Extra classes for CSS styling'),
                 value=self.extra_css_class, size=30, advanced=(not self.extra_css_class))
         form.add(ConditionWidget, 'condition', title=_('Display Condition'), value=self.condition,
                 required=False, size=50, django_only=True,
                 advanced=not(bool(self.condition)))
         form.add(CheckboxesWidget, 'display_locations', title=_('Display Locations'),
                 options=self.get_display_locations_options(),
diff --git a/wcs/qommon/form.py b/wcs/qommon/form.py
index 7c997e46..d436a6aa 100644
--- a/wcs/qommon/form.py
+++ b/wcs/qommon/form.py
@@ -571,27 +571,27 @@ class DurationWidget(StringWidget):
         return str(humanduration2seconds(self.value)) if value else None
 
 
 class TextWidget(quixote.form.TextWidget):
     def __init__(self, name, *args, **kwargs):
         self.validation_function = kwargs.pop('validation_function', None)
         super(TextWidget, self).__init__(name, *args, **kwargs)
 
-    def _parse(self, request):
+    def _parse(self, request, use_validation_function=True):
         quixote.form.TextWidget._parse(self, request)
         if self.value is not None:
             try:
                 maxlength = int(self.attrs.get('maxlength', 0))
             except (TypeError, ValueError):
                 maxlength = 0
             if maxlength:
                 if len(self.value) > maxlength:
                     self.error = _('too many characters (limit is %d)') % maxlength
-            if self.validation_function:
+            if use_validation_function and self.validation_function:
                 try:
                     self.validation_function(self.value)
                 except ValueError as e:
                     self.error = str(e)
 
 
 class CheckboxWidget(quixote.form.CheckboxWidget):
     '''
@@ -1549,30 +1549,35 @@ $("#%s").autocompleteArray([
     %s
     ],{mode:'multiple', multipleSeparator:', '});
 </script>''' % (id, known_tags))
         return r.getvalue()
 
 
 class WysiwygTextWidget(TextWidget):
     def _parse(self, request):
-        TextWidget._parse(self, request)
+        TextWidget._parse(self, request, use_validation_function=False)
         if self.value:
             if _sanitizeHTML:
                 self.value = _sanitizeHTML(self.value, get_request().charset, 'text/html')
             if self.value.startswith('<br />'):
                 self.value = self.value[6:]
             if self.value.endswith('<br />'):
                 self.value = self.value[:-6]
             # unescape Django template tags
             parser = HTMLParser()
             charset = get_publisher().site_charset
             def unquote_django(matchobj):
                 return force_str(parser.unescape(force_text(matchobj.group(0), charset)))
             self.value = re.sub('{[{%](.*?)[%}]}', unquote_django, self.value)
+            if self.validation_function:
+                try:
+                    self.validation_function(self.value)
+                except ValueError as e:
+                    self.error = str(e)
 
     def add_media(self):
         get_response().add_javascript(['qommon.wysiwyg.js'])
 
     def render_content(self):
         from ckeditor.widgets import DEFAULT_CONFIG as CKEDITOR_DEFAULT_CONFIG
         attrs = self.attrs.copy()
         config = copy.deepcopy(CKEDITOR_DEFAULT_CONFIG)
-- 
2.26.2

