From eb3c8c1d7ea03e80198646afb96635cc9f732cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Sat, 17 Dec 2022 22:53:11 +0900 Subject: [PATCH 1/5] Restore serving MessageDialogs as text/html Now that Zope uses text/plain by default, MessageDialogs were served as text/plain. Keep compatibility by returning a special string with `asHTML` method, that ZPublisher.HTTPResponse.HTTPResponse.setBody understands. MessageDialogs are deprecated and do not integrate well in Zope >= 4 ZMI, but they are used in some old products. --- CHANGES.rst | 2 ++ src/App/Dialogs.py | 15 ++++++++++++++- src/App/tests/testManagement.py | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9690e717f0..1d46772678 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,8 @@ https://zope.readthedocs.io/en/2.13/CHANGES.html 4.8.6 (unreleased) ------------------ +- Adjust the old ``App.Dialogs.MessageDialog`` to be served as HTML after + from PR https://github.com/zopefoundation/Zope/pull/1075 . 4.8.5 (2022-12-17) ------------------ diff --git a/src/App/Dialogs.py b/src/App/Dialogs.py index 500d9b0eda..c18a6a30cf 100644 --- a/src/App/Dialogs.py +++ b/src/App/Dialogs.py @@ -34,7 +34,20 @@ from App.special_dtml import HTML -MessageDialog = HTML(""" +class MessageDialogHTML(HTML): + """An special version of HTML which is always published as text/html + """ + def __call__(self, *args, **kw): + class _HTMLString(str): + """A special string that will be published as text/html + """ + def asHTML(self): + return self + return _HTMLString( + super(MessageDialogHTML, self).__call__(*args, **kw)) + + +MessageDialog = MessageDialogHTML(""" &dtml-title; diff --git a/src/App/tests/testManagement.py b/src/App/tests/testManagement.py index c2d57ff761..4df46013ba 100644 --- a/src/App/tests/testManagement.py +++ b/src/App/tests/testManagement.py @@ -94,3 +94,21 @@ def test_manage_content_type(self): self.folder.manage(req) self.assertIn('text/html', req.RESPONSE.getHeader('Content-Type')) + + +class TestDialogsMessageDialog(Testing.ZopeTestCase.ZopeTestCase): + + def test_publish_set_content_type(self): + from App.Dialogs import MessageDialog + + md = MessageDialog( + title='dialog title', + message='dialog message', + action='action' + ) + self.assertIn('dialog title', md) + self.assertIn('dialog message', md) + self.assertIn('action', md) + req = self.app.REQUEST + req.RESPONSE.setBody(md) + self.assertIn('text/html', req.RESPONSE.getHeader('Content-Type')) From 83acd8fc04656b54edecaed2c9961fc6d7e21ee2 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 18 Dec 2022 10:22:32 +0100 Subject: [PATCH 2/5] - move tests into separate module --- src/App/tests/testManagement.py | 18 ------------------ src/App/tests/test_Dialogs.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 src/App/tests/test_Dialogs.py diff --git a/src/App/tests/testManagement.py b/src/App/tests/testManagement.py index 4df46013ba..c2d57ff761 100644 --- a/src/App/tests/testManagement.py +++ b/src/App/tests/testManagement.py @@ -94,21 +94,3 @@ def test_manage_content_type(self): self.folder.manage(req) self.assertIn('text/html', req.RESPONSE.getHeader('Content-Type')) - - -class TestDialogsMessageDialog(Testing.ZopeTestCase.ZopeTestCase): - - def test_publish_set_content_type(self): - from App.Dialogs import MessageDialog - - md = MessageDialog( - title='dialog title', - message='dialog message', - action='action' - ) - self.assertIn('dialog title', md) - self.assertIn('dialog message', md) - self.assertIn('action', md) - req = self.app.REQUEST - req.RESPONSE.setBody(md) - self.assertIn('text/html', req.RESPONSE.getHeader('Content-Type')) diff --git a/src/App/tests/test_Dialogs.py b/src/App/tests/test_Dialogs.py new file mode 100644 index 0000000000..4b741b322c --- /dev/null +++ b/src/App/tests/test_Dialogs.py @@ -0,0 +1,19 @@ +import Testing.ZopeTestCase + + +class TestMessageDialog(Testing.ZopeTestCase.ZopeTestCase): + + def test_publish_set_content_type(self): + from App.Dialogs import MessageDialog + + md = MessageDialog( + title='dialog title', + message='dialog message', + action='action' + ) + self.assertIn('dialog title', md) + self.assertIn('dialog message', md) + self.assertIn('action', md) + req = self.app.REQUEST + req.RESPONSE.setBody(md) + self.assertIn('text/html', req.RESPONSE.getHeader('Content-Type')) From b8763d861d1b9392a9efd9172c7fb4ad9cf4a5d2 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 18 Dec 2022 11:55:13 +0100 Subject: [PATCH 3/5] - add fix for exception views --- CHANGES.rst | 6 ++++-- src/ZPublisher/WSGIPublisher.py | 5 +++++ src/ZPublisher/tests/test_WSGIPublisher.py | 24 ++++++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1d46772678..40e4baf558 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,8 +10,10 @@ https://zope.readthedocs.io/en/2.13/CHANGES.html 4.8.6 (unreleased) ------------------ -- Adjust the old ``App.Dialogs.MessageDialog`` to be served as HTML after - from PR https://github.com/zopefoundation/Zope/pull/1075 . +- Explicitly serve ``App.Dialogs.MessageDialog`` and exception views as HTML + due to the changed default content type from `#1075 + `_. + 4.8.5 (2022-12-17) ------------------ diff --git a/src/ZPublisher/WSGIPublisher.py b/src/ZPublisher/WSGIPublisher.py index 9aded018a5..a4ee56e873 100644 --- a/src/ZPublisher/WSGIPublisher.py +++ b/src/ZPublisher/WSGIPublisher.py @@ -144,6 +144,11 @@ def _exc_view_created_response(exc, request, response): for key, value in exc.headers.items(): response.setHeader(key, value) + # Explicitly set the content type header if it's not there yet so + # the response doesn't get served with the text/plain default + if not response.getHeader('Content-Type'): + response.setHeader('Content-Type', 'text/html') + # Set the response body to the result of calling the view. response.setBody(view()) return True diff --git a/src/ZPublisher/tests/test_WSGIPublisher.py b/src/ZPublisher/tests/test_WSGIPublisher.py index 1346578293..74d8618aee 100644 --- a/src/ZPublisher/tests/test_WSGIPublisher.py +++ b/src/ZPublisher/tests/test_WSGIPublisher.py @@ -670,6 +670,10 @@ def _request_factory(stdin, environ, response): self.assertTrue(app_iter[1].startswith( 'Exception View: ConflictError')) self.assertEqual(_request.retry_count, _request.retry_max_count) + + # The Content-Type response heade should be set to text/html + self.assertIn('text/html', _request.response.getHeader('Content-Type')) + unregisterExceptionView(Exception) def testCustomExceptionViewUnauthorized(self): @@ -871,10 +875,10 @@ def _callFUT(self, exc): from zope.interface import directlyProvides from zope.publisher.browser import IDefaultBrowserLayer from ZPublisher.WSGIPublisher import _exc_view_created_response - req = DummyRequest() + req = self.app.REQUEST req['PARENTS'] = [self.app] directlyProvides(req, IDefaultBrowserLayer) - return _exc_view_created_response(exc, req, DummyResponse()) + return _exc_view_created_response(exc, req, req.RESPONSE) def _registerStandardErrorView(self): from OFS.browser import StandardErrorMessageView @@ -901,14 +905,22 @@ def testWithStandardErrorMessage(self): from OFS.DTMLMethod import addDTMLMethod from zExceptions import NotFound self._registerStandardErrorView() + response = self.app.REQUEST.RESPONSE addDTMLMethod(self.app, 'standard_error_message', file='OOPS') + # The response content-type header is not set before rendering + # the standard error template + self.assertFalse(response.getHeader('Content-Type')) + try: self.assertTrue(self._callFUT(NotFound)) finally: self._unregisterStandardErrorView() + # After rendering the response content-type header is set + self.assertIn('text/html', response.getHeader('Content-Type')) + class WSGIPublisherTests(FunctionalTestCase): @@ -1063,6 +1075,14 @@ def setStatus(self, status, reason=None, lock=None): status = property(lambda self: self._status, setStatus) + def getHeader(self, header): + return dict(self._headers).get(header, None) + + def setHeader(self, header, value): + headers = dict(self._headers) + headers[header] = value + self._headers = tuple(headers.items()) + class DummyCallable(object): _called_with = _raise = _result = None From 287278d468392f5bf7165477602f08810d02130d Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 18 Dec 2022 11:56:53 +0100 Subject: [PATCH 4/5] - typo [ci skip] --- src/App/Dialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/Dialogs.py b/src/App/Dialogs.py index c18a6a30cf..8687dbacf6 100644 --- a/src/App/Dialogs.py +++ b/src/App/Dialogs.py @@ -35,7 +35,7 @@ class MessageDialogHTML(HTML): - """An special version of HTML which is always published as text/html + """A special version of HTML which is always published as text/html """ def __call__(self, *args, **kw): class _HTMLString(str): From d70ab2ce50f8aafa102570f5d3fcd5382c690594 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 18 Dec 2022 11:58:34 +0100 Subject: [PATCH 5/5] - typo [ci skip] --- src/ZPublisher/tests/test_WSGIPublisher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZPublisher/tests/test_WSGIPublisher.py b/src/ZPublisher/tests/test_WSGIPublisher.py index 74d8618aee..bb65df74b3 100644 --- a/src/ZPublisher/tests/test_WSGIPublisher.py +++ b/src/ZPublisher/tests/test_WSGIPublisher.py @@ -671,7 +671,7 @@ def _request_factory(stdin, environ, response): 'Exception View: ConflictError')) self.assertEqual(_request.retry_count, _request.retry_max_count) - # The Content-Type response heade should be set to text/html + # The Content-Type response header should be set to text/html self.assertIn('text/html', _request.response.getHeader('Content-Type')) unregisterExceptionView(Exception)