Commit ef6a6622 authored by Rafael Monnerat's avatar Rafael Monnerat

slapos_rss_style: convert to title, description, author to compatible xml

  Ensure to convert the text values to a safer output that will be compatible with XML 1.0.
  See: https://www.w3.org/TR/REC-xml/#NT-Char
parent 032ab585
Pipeline #37724 failed with stage
in 0 seconds
from zExceptions import Unauthorized
from Products.Formulator.Widget import convert_to_xml_compatible_string
def convertToSafeXML(self, value, REQUEST=None):
if REQUEST is not None:
raise Unauthorized
if not value:
return value
return convert_to_xml_compatible_string(value)
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Extension Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>SlapOSRSS</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>extension.erp5.SlapOSRSS</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Extension Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ExternalMethod" module="Products.ExternalMethod.ExternalMethod"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_function</string> </key>
<value> <string>convertToSafeXML</string> </value>
</item>
<item>
<key> <string>_module</string> </key>
<value> <string>SlapOSRSS</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_convertToSafeXML</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -56,11 +56,13 @@ for brain in portal.portal_catalog( ...@@ -56,11 +56,13 @@ for brain in portal.portal_catalog(
data_list.append( data_list.append(
Object(**{ Object(**{
'title': "[%s] %s" % (ticket_category.upper(), ticket_title), 'title': context.Base_convertToSafeXML(
"[%s] %s" % (ticket_category.upper(), ticket_title)),
'category': ticket_category, 'category': ticket_category,
'author': event.getSourceTitle(checked_permission="View"), 'author': context.Base_convertToSafeXML(
event.getSourceTitle(checked_permission="View")),
'link': ticket_link, 'link': ticket_link,
'description': event.getTextContent(), 'description': context.Base_convertToSafeXML(event.getTextContent()),
'pubDate': event.getStartDate(), 'pubDate': event.getStartDate(),
'guid': '{}-{}'.format( 'guid': '{}-{}'.format(
event.getFollowUp(), event.getFollowUp(),
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixinWithAbort,\ from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixinWithAbort,\
TemporaryAlarmScript, PinnedDateTime TemporaryAlarmScript, PinnedDateTime
from Products.ERP5Type.Utils import unicode2str
from DateTime import DateTime from DateTime import DateTime
import feedparser import feedparser
from time import sleep from time import sleep
...@@ -133,9 +134,53 @@ class TestRSSSyleSkinsMixin(SlapOSTestCaseMixinWithAbort): ...@@ -133,9 +134,53 @@ class TestRSSSyleSkinsMixin(SlapOSTestCaseMixinWithAbort):
self.tic() self.tic()
return support_request return support_request
class TestSlapOSSupportRequestRSS(TestRSSSyleSkinsMixin): class TestSlapOSSupportRequestRSS(TestRSSSyleSkinsMixin):
def test_WebSection_viewTicketListAsRSS_invalid_char(self):
person = self.makePerson(self.addProject())
support_request = person.Entity_createTicketFromTradeCondition(
'service_module/slapos_crm_monitoring',
'Broken \08x',
'I need help !\08x',
)
support_request.Ticket_createProjectEvent(
support_request.getTitle(), 'incoming', 'Web Message',
support_request.getResource(),
text_content=support_request.getDescription(),
content_type='text/plain',
source=person.getRelativeUrl()
)
self.tic()
self.login(person.getUserId())
self.portal.portal_skins.changeSkin('RSS')
parsed = feedparser.parse(self.portal.WebSection_viewTicketListAsRSS())
self.assertFalse(parsed.bozo)
first_entry_id = [item.id for item in parsed.entries]
self.assertEqual([item.summary for item in parsed.entries],
[u'I need help !\ufffd8x'])
self.logout()
sleep(2)
self.login()
support_request.Ticket_createProjectEvent(
support_request.getTitle(), 'outgoing', 'Web Message',
support_request.getResource(),
text_content='How can I \08xhelp you ?',
content_type='text/plain'
)
self.tic()
self.logout()
self.login(person.getUserId())
self.portal.portal_skins.changeSkin('RSS')
parsed = feedparser.parse(self.portal.WebSection_viewTicketListAsRSS())
self.assertFalse(parsed.bozo)
self.assertEqual([item.summary for item in parsed.entries],
[u'How can I \ufffd8xhelp you ?', u'I need help !\ufffd8x'])
self.assertNotEqual([item.id for item in parsed.entries][0], first_entry_id)
def test_WebSection_viewTicketListAsRSS(self): def test_WebSection_viewTicketListAsRSS(self):
person = self.makePerson(self.addProject()) person = self.makePerson(self.addProject())
...@@ -195,7 +240,7 @@ class TestSlapOSWebSection_getEventList(TestRSSSyleSkinsMixin): ...@@ -195,7 +240,7 @@ class TestSlapOSWebSection_getEventList(TestRSSSyleSkinsMixin):
def test_WebSection_getEventList(self, web_site=None): def test_WebSection_getEventList(self, web_site=None):
# WebSection_getEventList is already widely tested on Base_getTicketRelatedEventList # WebSection_getEventList is already widely tested on Base_getTicketRelatedEventList
# and Folder_getOpenTicketList, so we only tested the specific use case of # and Folder_getOpenTicketList, so we only tested the specific use case of
# all events togheter # all events togheter
if web_site is None: if web_site is None:
web_site = self.portal web_site = self.portal
...@@ -251,11 +296,12 @@ class TestSlapOSWebSection_getEventList(TestRSSSyleSkinsMixin): ...@@ -251,11 +296,12 @@ class TestSlapOSWebSection_getEventList(TestRSSSyleSkinsMixin):
# Extra checks # Extra checks
self.assertNotEqual(open_ticket_list[0].pubDate, None) self.assertNotEqual(open_ticket_list[0].pubDate, None)
self.assertNotEqual(open_ticket_list[0].link, None) self.assertNotEqual(open_ticket_list[0].link, None)
self.assertIn(event.getTextContent(), open_ticket_list[0].description) self.assertIn(event.getTextContent(),
unicode2str(open_ticket_list[0].description))
self.assertEqual(open_ticket_list[0].guid, self.assertEqual(open_ticket_list[0].guid,
'{}-{}'.format(event.getFollowUp(), '{}-{}'.format(event.getFollowUp(),
event.getRelativeUrl())) event.getRelativeUrl()))
self.assertEqual(open_ticket_list[0].title, self.assertEqual(unicode2str(open_ticket_list[0].title),
'[MONITORING] %s' % ticket.getTitle()) '[MONITORING] %s' % ticket.getTitle())
self.assertIn("%s/#/" % web_site.absolute_url(), self.assertIn("%s/#/" % web_site.absolute_url(),
open_ticket_list[0].link) open_ticket_list[0].link)
...@@ -317,11 +363,12 @@ class TestSlapOSWebSection_getEventList(TestRSSSyleSkinsMixin): ...@@ -317,11 +363,12 @@ class TestSlapOSWebSection_getEventList(TestRSSSyleSkinsMixin):
# Extra checks # Extra checks
self.assertNotEqual(open_ticket_list[0].pubDate, None) self.assertNotEqual(open_ticket_list[0].pubDate, None)
self.assertNotEqual(open_ticket_list[0].link, None) self.assertNotEqual(open_ticket_list[0].link, None)
self.assertIn(event_rr.getTextContent(), open_ticket_list[0].description) self.assertIn(event_rr.getTextContent(),
unicode2str(open_ticket_list[0].description))
self.assertEqual(open_ticket_list[0].guid, self.assertEqual(open_ticket_list[0].guid,
'{}-{}'.format(event_rr.getFollowUp(), '{}-{}'.format(event_rr.getFollowUp(),
event_rr.getRelativeUrl())) event_rr.getRelativeUrl()))
self.assertEqual(open_ticket_list[0].title, self.assertEqual(unicode2str(open_ticket_list[0].title),
'[ACKNOWLEDGEMENT] %s' % regularisation_request.getTitle()) '[ACKNOWLEDGEMENT] %s' % regularisation_request.getTitle())
self.assertIn("%s/#/" % web_site.absolute_url(), self.assertIn("%s/#/" % web_site.absolute_url(),
open_ticket_list[0].link) open_ticket_list[0].link)
...@@ -408,19 +455,19 @@ class TestSlapOSWebSection_getEventList(TestRSSSyleSkinsMixin): ...@@ -408,19 +455,19 @@ class TestSlapOSWebSection_getEventList(TestRSSSyleSkinsMixin):
event_ud.getRelativeUrl())) event_ud.getRelativeUrl()))
# check if ordering is correct. # check if ordering is correct.
self.assertEqual(open_ticket_list[0].title, self.assertEqual(unicode2str(open_ticket_list[0].title),
'[THEIA IDE] %s' % upgrade_decision.getTitle()) '[THEIA IDE] %s' % upgrade_decision.getTitle())
self.assertIn("%s/#/" % web_site.absolute_url(), self.assertIn("%s/#/" % web_site.absolute_url(),
open_ticket_list[1].link) open_ticket_list[1].link)
self.assertEqual(open_ticket_list[1].title, self.assertEqual(unicode2str(open_ticket_list[1].title),
'[ACKNOWLEDGEMENT] %s' % regularisation_request.getTitle()) '[ACKNOWLEDGEMENT] %s' % regularisation_request.getTitle())
self.assertIn("%s/#/" % web_site.absolute_url(), self.assertIn("%s/#/" % web_site.absolute_url(),
open_ticket_list[1].link) open_ticket_list[1].link)
self.assertEqual(open_ticket_list[2].title, self.assertEqual(unicode2str(open_ticket_list[2].title),
'[MONITORING] %s' % ticket.getTitle()) '[MONITORING] %s' % ticket.getTitle())
self.assertIn("%s/#/" % web_site.absolute_url(), self.assertIn("%s/#/" % web_site.absolute_url(),
...@@ -481,7 +528,7 @@ class TestWebSection_getLegacyMessageList(TestRSSSyleSkinsMixin): ...@@ -481,7 +528,7 @@ class TestWebSection_getLegacyMessageList(TestRSSSyleSkinsMixin):
self.assertEqual('This RSS is disabled (20241001)', message.title) self.assertEqual('This RSS is disabled (20241001)', message.title)
self.assertIn('This RSS feed is disabled:', message.description) self.assertIn('This RSS feed is disabled:', message.description)
self.assertIn('?date=20241001', message.link) self.assertIn('?date=20241001', message.link)
def testWebSection_getLegacyMessageList_instance_tree(self): def testWebSection_getLegacyMessageList_instance_tree(self):
# Test instance tree legacy since we patch InstanceTree_view # Test instance tree legacy since we patch InstanceTree_view
web_site = self.portal.web_site_module.slapos_master_panel.Base_createCloneDocument(batch_mode=1) web_site = self.portal.web_site_module.slapos_master_panel.Base_createCloneDocument(batch_mode=1)
...@@ -503,10 +550,4 @@ class TestWebSection_getLegacyMessageList(TestRSSSyleSkinsMixin): ...@@ -503,10 +550,4 @@ class TestWebSection_getLegacyMessageList(TestRSSSyleSkinsMixin):
self.assertEqual(message.author, 'Administrator') self.assertEqual(message.author, 'Administrator')
self.assertEqual('This RSS is disabled (20241001)', message.title) self.assertEqual('This RSS is disabled (20241001)', message.title)
self.assertIn('This RSS feed is disabled:', message.description) self.assertIn('This RSS feed is disabled:', message.description)
self.assertIn('?date=20241001', message.link) self.assertIn('?date=20241001', message.link)
\ No newline at end of file
\ No newline at end of file
extension.erp5.SlapOSRSS
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment