From dca2da0b45520570616d5a7732d5a29fb76f0d5a Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Wed, 1 Jun 2022 18:23:06 +0200 Subject: [PATCH 1/8] Begin to implement escape=True parameter See #MOD-913 --- .../eeafaceted/z3ctable/browser/views.py | 3 +++ src/collective/eeafaceted/z3ctable/columns.py | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/collective/eeafaceted/z3ctable/browser/views.py b/src/collective/eeafaceted/z3ctable/browser/views.py index fa74b98..de9ec83 100644 --- a/src/collective/eeafaceted/z3ctable/browser/views.py +++ b/src/collective/eeafaceted/z3ctable/browser/views.py @@ -13,6 +13,7 @@ from zope.component import queryMultiAdapter from zope.interface import implements +import html import logging import traceback @@ -80,6 +81,8 @@ def renderCell(self, item, column, colspan=0): colspanStr = colspan and ' colspan="%s"' % colspan or '' start = datetime.now() renderedCell = column.renderCell(item) + if column.escape: + renderedCell = html.escape(renderedCell) if self.debug: if not hasattr(column, 'cumulative_time'): column.cumulative_time = timedelta(0) diff --git a/src/collective/eeafaceted/z3ctable/columns.py b/src/collective/eeafaceted/z3ctable/columns.py index 9a4f816..05d8a34 100644 --- a/src/collective/eeafaceted/z3ctable/columns.py +++ b/src/collective/eeafaceted/z3ctable/columns.py @@ -18,6 +18,7 @@ from zope.interface import implements from zope.schema.interfaces import IVocabularyFactory +import html import os import pkg_resources import urllib @@ -57,6 +58,8 @@ class BaseColumn(column.GetAttrColumn): header_help = None # enable caching, needs to be implemented by Column use_caching = True + # escape + escape = True @property def cssClasses(self): @@ -346,6 +349,8 @@ class BrowserViewCallColumn(BaseColumn): sort_index = -1 params = {} view_name = None + # manage escape manually + escape = False def renderCell(self, item): if not self.view_name: @@ -407,6 +412,8 @@ class AbbrColumn(VocabularyColumn): # named utility full_vocabulary = None + # manage escape manually + escape = False def renderCell(self, item): value = self.getValue(item) @@ -446,8 +453,8 @@ def renderCell(self, item): tag_title = self._cached_full_vocab_instance.getTerm(v).title tag_title = tag_title.replace("'", "'") res.append(u"{1}".format( - safe_unicode(tag_title), - safe_unicode(self._cached_acronym_vocab_instance.getTerm(v).title))) + html.escape(safe_unicode(tag_title)), + html.escape(safe_unicode(self._cached_acronym_vocab_instance.getTerm(v).title)))) except LookupError: # in case an element is not in the vocabulary, add the value res.append(safe_unicode(v)) @@ -489,6 +496,8 @@ class CheckBoxColumn(BaseColumn): checked_by_default = True attrName = 'UID' weight = 100 + # not necessary to escape, everything is generated + escape = False def renderHeadCell(self): """ """ @@ -596,6 +605,9 @@ class PrettyLinkColumn(TitleColumn): """A column that displays the IPrettyLink.getLink column. This rely on imio.prettylink.""" + # manage escape manually + escape = False + params = {} @property From 27c0fd09449738b2d97bcab18f852dc399c4dfb4 Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Thu, 2 Jun 2022 14:24:30 +0200 Subject: [PATCH 2/8] Base implementation of `escape` --- src/collective/eeafaceted/z3ctable/columns.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/collective/eeafaceted/z3ctable/columns.py b/src/collective/eeafaceted/z3ctable/columns.py index 05d8a34..d13432d 100644 --- a/src/collective/eeafaceted/z3ctable/columns.py +++ b/src/collective/eeafaceted/z3ctable/columns.py @@ -349,8 +349,6 @@ class BrowserViewCallColumn(BaseColumn): sort_index = -1 params = {} view_name = None - # manage escape manually - escape = False def renderCell(self, item): if not self.view_name: @@ -367,6 +365,8 @@ class VocabularyColumn(BaseColumn): # named utility vocabulary = None ignored_value = EMPTY_STRING + # we escape here + escape = False def renderCell(self, item): value = self.getValue(item) @@ -396,7 +396,7 @@ def renderCell(self, item): res = [] for v in value: try: - res.append(safe_unicode(self._cached_vocab_instance.getTerm(v).title)) + res.append(html.escape(safe_unicode(self._cached_vocab_instance.getTerm(v).title))) except LookupError: # in case an element is not in the vocabulary, add the value res.append(safe_unicode(v)) @@ -412,7 +412,8 @@ class AbbrColumn(VocabularyColumn): # named utility full_vocabulary = None - # manage escape manually + separator = u', ' + # we manage escape here manually escape = False def renderCell(self, item): @@ -457,9 +458,9 @@ def renderCell(self, item): html.escape(safe_unicode(self._cached_acronym_vocab_instance.getTerm(v).title)))) except LookupError: # in case an element is not in the vocabulary, add the value - res.append(safe_unicode(v)) + res.append(html.escape(safe_unicode(v))) - res = ', '.join(res) + res = self.separator.join(res) if self.use_caching: self._store_cached_result(value, res) return res @@ -566,7 +567,7 @@ def renderCell(self, item): # this column may also be used with more classical z3c.table # where there is no batch and we display the entire table values_uids = [v.UID for v in self.table.values] - return start + values_uids.index(item.UID) + return str(start + values_uids.index(item.UID)) ############################################################ @@ -605,7 +606,7 @@ class PrettyLinkColumn(TitleColumn): """A column that displays the IPrettyLink.getLink column. This rely on imio.prettylink.""" - # manage escape manually + # escape is managed by imio.prettylink escape = False params = {} From b325e48716c3aa818f125ed0d93d0cb33eea08b5 Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Tue, 7 Jun 2022 10:08:52 +0200 Subject: [PATCH 3/8] Completed changelog --- CHANGES.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 192c170..025e501 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,8 +5,11 @@ Changelog 2.18 (unreleased) ----------------- -- Nothing changed yet. - +- Added `BaseColumn.escape = True` so content is escaped. + Manage escape manually for the `VocabularyColumn` and the `AbbrColumn`, + set it to `False` for `CheckBoxColumn` that is entirely generated, set it to + `False` for `PrettyLinkColumnNothing` as `imio.prettylink` manages it itself. + [gbastien] 2.17 (2022-05-13) ----------------- From 6d4916b6b4c5dcbfbea4db368220b93e6f108989 Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Tue, 7 Jun 2022 11:54:07 +0200 Subject: [PATCH 4/8] Set `escape = False` by default for ActionsColumn --- CHANGES.rst | 5 +++-- src/collective/eeafaceted/z3ctable/columns.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 025e501..c1209a5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,8 +7,9 @@ Changelog - Added `BaseColumn.escape = True` so content is escaped. Manage escape manually for the `VocabularyColumn` and the `AbbrColumn`, - set it to `False` for `CheckBoxColumn` that is entirely generated, set it to - `False` for `PrettyLinkColumnNothing` as `imio.prettylink` manages it itself. + set it to `False` for `CheckBoxColumn` and `ActionsColumn` that are entirely + generated, set it to `False` for `PrettyLinkColumnNothing` as + `imio.prettylink` manages it itself. [gbastien] 2.17 (2022-05-13) diff --git a/src/collective/eeafaceted/z3ctable/columns.py b/src/collective/eeafaceted/z3ctable/columns.py index d13432d..8bd5617 100644 --- a/src/collective/eeafaceted/z3ctable/columns.py +++ b/src/collective/eeafaceted/z3ctable/columns.py @@ -776,6 +776,8 @@ class ActionsColumn(BrowserViewCallColumn): 'jQuery(document).ready(preventDefaultClickTransition);' view_name = 'actions_panel' params = {'showHistory': True, 'showActions': True} + # not necessary to escape, everything is generated + escape = False class IconsColumn(BaseColumn): From 4b79c414faf65d4bb278e4e4f609866041c47b73 Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Tue, 7 Jun 2022 12:14:18 +0200 Subject: [PATCH 5/8] Do not escape DateColumn and manage escape of TitleColumn manually --- CHANGES.rst | 8 ++++---- src/collective/eeafaceted/z3ctable/columns.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c1209a5..8f421c0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,10 +6,10 @@ Changelog ----------------- - Added `BaseColumn.escape = True` so content is escaped. - Manage escape manually for the `VocabularyColumn` and the `AbbrColumn`, - set it to `False` for `CheckBoxColumn` and `ActionsColumn` that are entirely - generated, set it to `False` for `PrettyLinkColumnNothing` as - `imio.prettylink` manages it itself. + Manage escape manually for the `TitleColumn`, `VocabularyColumn` and the + `AbbrColumn`, set it to `False` for `CheckBoxColumn`, `ElementNumberColumn` + and `ActionsColumn` that are entirely generated, set it to `False` for + `PrettyLinkColumnNothing` as `imio.prettylink` manages it itself. [gbastien] 2.17 (2022-05-13) diff --git a/src/collective/eeafaceted/z3ctable/columns.py b/src/collective/eeafaceted/z3ctable/columns.py index 8bd5617..9591393 100644 --- a/src/collective/eeafaceted/z3ctable/columns.py +++ b/src/collective/eeafaceted/z3ctable/columns.py @@ -297,6 +297,8 @@ class DateColumn(BaseColumn): long_format = False time_only = False ignored_value = EMPTY_DATE + # not necessary to escape, everything is generated + escape = False def renderCell(self, item): value = self.getValue(item) @@ -552,6 +554,8 @@ def renderCell(self, item): class ElementNumberColumn(BaseColumn): header = u'' + # not necessary to escape, everything is generated + escape = False def renderCell(self, item): """ """ @@ -567,7 +571,7 @@ def renderCell(self, item): # this column may also be used with more classical z3c.table # where there is no batch and we display the entire table values_uids = [v.UID for v in self.table.values] - return str(start + values_uids.index(item.UID)) + return start + values_uids.index(item.UID) ############################################################ @@ -593,13 +597,15 @@ class TitleColumn(BaseColumn): header = _('header_Title') sort_index = 'sortable_title' weight = 0 + # we manage escape here manually + escape = False def renderCell(self, item): value = self.getValue(item) if not value: value = u'-' value = safe_unicode(value) - return u'{1}'.format(item.getURL(), value) + return u'{1}'.format(item.getURL(), html.escape(value)) class PrettyLinkColumn(TitleColumn): From 2647e55cd3e3ed9060e12a78fa3dbb04d750b7d3 Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Tue, 7 Jun 2022 17:03:14 +0200 Subject: [PATCH 6/8] Set escape=False for ColorColumn and manage it manually See #MOD-913 --- src/collective/eeafaceted/z3ctable/columns.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/collective/eeafaceted/z3ctable/columns.py b/src/collective/eeafaceted/z3ctable/columns.py index 9591393..e362cf2 100644 --- a/src/collective/eeafaceted/z3ctable/columns.py +++ b/src/collective/eeafaceted/z3ctable/columns.py @@ -367,7 +367,7 @@ class VocabularyColumn(BaseColumn): # named utility vocabulary = None ignored_value = EMPTY_STRING - # we escape here + # we manage escape here manually escape = False def renderCell(self, item): @@ -384,11 +384,12 @@ def renderCell(self, item): # the vocabulary instance is cached if not hasattr(self, '_cached_vocab_instance'): if not self.vocabulary: - raise KeyError('A "vocabulary" must be defined for column "{0}" !'.format(self.attrName)) + raise KeyError('A "vocabulary" must be defined for column "{0}" !'.format( + self.attrName)) factory = queryUtility(IVocabularyFactory, self.vocabulary) if not factory: - raise KeyError('The vocabulary "{0}" used for column "{1}" was not found !'.format(self.vocabulary, - self.attrName)) + raise KeyError('The vocabulary "{0}" used for column "{1}" was not found !'.format( + self.vocabulary, self.attrName)) self._cached_vocab_instance = factory(self.context) @@ -477,17 +478,19 @@ class ColorColumn(I18nColumn): # Hide the head cell but fill it with spaces so it does # not shrink to nothing if table is too large header = u'   ' + # we manage escape here manually + escape = False def renderCell(self, item): """Display a message.""" translated_msg = super(ColorColumn, self).renderCell(item) - return u'
 
'.format(translated_msg) + return u'
 
'.format(html.escape(translated_msg)) def getCSSClasses(self, item): """Generate a CSS class to apply on the TD depending on the value.""" return {'td': "{0}_{1}_{2}".format(self.cssClassPrefix, str(self.attrName), - self.getValue(item))} + html.escape(self.getValue(item)))} class CheckBoxColumn(BaseColumn): From 5f17f7f1548b6073105436ad61acac52343e92fb Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Tue, 7 Jun 2022 17:17:20 +0200 Subject: [PATCH 7/8] Added basic test for BaseColumn.escape --- .../eeafaceted/z3ctable/tests/test_columns.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/collective/eeafaceted/z3ctable/tests/test_columns.py b/src/collective/eeafaceted/z3ctable/tests/test_columns.py index a9bbcb4..36717fb 100644 --- a/src/collective/eeafaceted/z3ctable/tests/test_columns.py +++ b/src/collective/eeafaceted/z3ctable/tests/test_columns.py @@ -35,6 +35,9 @@ from zope.intid.interfaces import IIntIds +import html + + class TestColumns(IntegrationTestCase): def test_default_columns_registration(self): @@ -653,6 +656,19 @@ def test_IconsColumn(self): u' ' u'') + def test_escape(self): + table = self.faceted_z3ctable_view + column = BaseColumn(self.portal, self.portal.REQUEST, table) + column.attrName = u'Title' + malicious = 'Malicious">' + self.portal.eea_folder.setTitle(malicious) + self.portal.eea_folder.reindexObject() + brains = self.portal.portal_catalog(UID=self.portal.eea_folder.UID()) + batch = Batch(brains, size=5) + table.update(batch) + self.assertFalse(malicious in table.render()) + self.assertTrue(html.escape(malicious) in table.render()) + class BrainsWithoutBatchTable(Table): """ """ From 55cbbebea4119c863c51e1b0dc39dd1f316e1652 Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Thu, 9 Jun 2022 08:48:34 +0200 Subject: [PATCH 8/8] Use sources for imio.prettylink (there is a fix there for escape) --- buildout.d/dev.cfg | 1 + buildout.d/sources.cfg | 14 ++++++++++++++ .../eeafaceted/z3ctable/tests/test_columns.py | 1 - 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/buildout.d/dev.cfg b/buildout.d/dev.cfg index 1c2433a..818cbcc 100755 --- a/buildout.d/dev.cfg +++ b/buildout.d/dev.cfg @@ -31,6 +31,7 @@ extensions += mr.developer sources = sources always-checkout = force auto-checkout = + imio.prettylink [code-analysis] recipe = plone.recipe.codeanalysis diff --git a/buildout.d/sources.cfg b/buildout.d/sources.cfg index 57c2f8d..c1c7b45 100644 --- a/buildout.d/sources.cfg +++ b/buildout.d/sources.cfg @@ -1 +1,15 @@ +[remotes] +collective = https://github.com/collective +collective_push = git@github.com:collective +plone = https://github.com/plone +plone_push = git@github.com:plone +ftw = https://github.com/4teamwork +ftw_push = git@github.com:4teamwork +imio = https://github.com/IMIO +imio_push = git@github.com:IMIO +zopefoundation = https://github.com/zopefoundation +zopefoundation_push = git@github.com:zopefoundation +zopesvn = svn://svn.zope.org/repos/main/ + [sources] +imio.prettylink = git ${remotes:imio}/imio.prettylink.git pushurl=${remotes:imio_push}/imio.prettylink.git diff --git a/src/collective/eeafaceted/z3ctable/tests/test_columns.py b/src/collective/eeafaceted/z3ctable/tests/test_columns.py index 36717fb..7a6c6f9 100644 --- a/src/collective/eeafaceted/z3ctable/tests/test_columns.py +++ b/src/collective/eeafaceted/z3ctable/tests/test_columns.py @@ -34,7 +34,6 @@ from zope.component import queryMultiAdapter from zope.intid.interfaces import IIntIds - import html