diff --git a/plone/app/z3cform/converters.py b/plone/app/z3cform/converters.py index 8693fe23..21d7174e 100644 --- a/plone/app/z3cform/converters.py +++ b/plone/app/z3cform/converters.py @@ -8,7 +8,7 @@ from plone.app.z3cform.interfaces import IQueryStringWidget from plone.app.z3cform.interfaces import IRelatedItemsWidget from plone.app.z3cform.interfaces import ISelectWidget -from plone.app.z3cform.utils import replace_link_variables_by_paths +from plone.app.z3cform import utils from plone.uuid.interfaces import IUUID from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import safe_callable @@ -27,6 +27,7 @@ import json import pytz +import urlparse @adapter(IDate, IDateWidget) @@ -313,12 +314,14 @@ class LinkWidgetDataConverter(BaseDataConverter): def toWidgetValue(self, value): value = super(LinkWidgetDataConverter, self).toWidgetValue(value) - result = {'internal': u'', - 'external': u'', - 'email': u'', - 'email_subject': u''} - uuid = None + result = { + 'internal': u'', + 'external': u'', + 'email': u'', + 'email_subject': u'' + } if value.startswith('mailto:'): + # Handle mail URLs value = value[7:] # strip mailto from beginning if '?subject=' in value: email, email_subject = value.split('?subject=') @@ -327,12 +330,19 @@ def toWidgetValue(self, value): else: result['email'] = value else: - if '/resolveuid/' in value: + uuid = None + portal = getSite() + is_same_domain = utils.is_same_domain(value, portal.absolute_url()) + is_absolute = utils.is_absolute(value) + if '/resolveuid/' in value and (not is_absolute or is_same_domain): + # Take the UUID part of a resolveuid url, but onl if it's on + # the same domain. result['internal'] = value.rsplit('/', 1)[-1] - else: - portal = getSite() - path = replace_link_variables_by_paths(portal, value) - path = path[len(portal.absolute_url())+1:].encode('ascii', 'ignore') # noqa + elif not is_absolute or is_absolute and is_same_domain: + # Handdle relative URLs or absolute URLs on the same domain. + path = urlparse.urlparse(value).path + path = utils.replace_link_variables_by_paths(portal, path) + path = path.encode('ascii', 'ignore') obj = portal.unrestrictedTraverse(path=path, default=None) if obj is not None: uuid = IUUID(obj, None) diff --git a/plone/app/z3cform/tests/test_utils.py b/plone/app/z3cform/tests/test_utils.py index 07a7c870..f3b703fe 100644 --- a/plone/app/z3cform/tests/test_utils.py +++ b/plone/app/z3cform/tests/test_utils.py @@ -91,7 +91,34 @@ def test_complex(self): self.assertEqual(test_out, test_compare) -def test_suite(): - return unittest.TestSuite([ - unittest.makeSuite(TestUnitCallCallables), - ]) +class TestUtils(unittest.TestCase): + + def test_is_absolute(self): + from plone.app.z3cform.utils import is_absolute + + self.assertTrue(is_absolute('https://plone.org/')) + self.assertTrue(is_absolute('http://plone.org/')) + self.assertTrue(is_absolute('webdav://plone.org/')) + self.assertTrue(not is_absolute('./path/to/site')) + self.assertTrue(not is_absolute('/resolveuid/')) + + def test_is_same_domain(self): + from plone.app.z3cform.utils import is_same_domain + + # Those use the same protocol and are on the same domaain + self.assertTrue(is_same_domain( + 'https://plone.org/doc1', + 'https://plone.org/doc2/doc3' + )) + + # These are two completly different URLs + self.assertTrue(not is_same_domain( + 'https://domain1.com', + 'https://anotherdomain.com' + )) + + # Here, different transport protocols are used. Returning False. + self.assertTrue(not is_same_domain( + 'https://plone.org', + 'http://plone.org' + )) diff --git a/plone/app/z3cform/tests/test_widgets.py b/plone/app/z3cform/tests/test_widgets.py index 977a7bc3..f783c2a4 100644 --- a/plone/app/z3cform/tests/test_widgets.py +++ b/plone/app/z3cform/tests/test_widgets.py @@ -9,6 +9,7 @@ from plone.dexterity.fti import DexterityFTI from plone.registry.interfaces import IRegistry from plone.testing.zca import UNIT_TESTING +from plone.uuid.interfaces import IUUID from Products.CMFPlone.interfaces import IMarkupSchema from z3c.form.form import Form from z3c.form.interfaces import IFormLayer @@ -1502,3 +1503,67 @@ def test_link_widget__extract_email_including_mailto(self): widget.extract(), u'mailto:dev@plone.org' ) + + def test_link_widget__data_converter(self): + from plone.app.z3cform.widget import LinkWidget + from plone.app.z3cform.converters import LinkWidgetDataConverter + + field = TextLine(__name__='linkfield') + widget = LinkWidget(self.request) + converter = LinkWidgetDataConverter(field, widget) + + self.portal.invokeFactory('Folder', 'test') + portal_url = self.portal.absolute_url() + portal_path = '/'.join(self.portal.getPhysicalPath()) + + # Test external URLs + self.assertEqual( + converter.toWidgetValue(u'https://plone.org')['external'], + u'https://plone.org' + ) + + # Test relative resolveuid URLs + self.assertEqual( + converter.toWidgetValue(u'/resolveuid/1234')['internal'], + u'1234' + ) + + # Test absolute resolveuid URLs on the same domain + self.assertEqual( + converter.toWidgetValue(portal_url + '/resolveuid/1234')['internal'], # noqa + u'1234' + ) + + # Test absolute resolveuid URLs on a different domain + self.assertEqual( + converter.toWidgetValue(u'http://anyurl/resolveuid/1234')['external'], # noqa + u'http://anyurl/resolveuid/1234' + ) + + # Test interrnal URL paths + self.assertEqual( + converter.toWidgetValue(portal_path + '/test')['internal'], + IUUID(self.portal.test) + ) + + # Test absolute interrnal URLs + self.assertEqual( + converter.toWidgetValue(portal_url + '/test')['internal'], + IUUID(self.portal.test) + ) + + # Test mail + self.assertEqual( + converter.toWidgetValue(u'mailto:me')['email'], + u'me' + ) + + # Test mail with subject + self.assertEqual( + converter.toWidgetValue(u'mailto:me?subject=jep')['email'], + u'me' + ) + self.assertEqual( + converter.toWidgetValue(u'mailto:me?subject=jep')['email_subject'], + u'jep' + ) diff --git a/plone/app/z3cform/utils.py b/plone/app/z3cform/utils.py index f7ee27a2..a62070d8 100644 --- a/plone/app/z3cform/utils.py +++ b/plone/app/z3cform/utils.py @@ -4,6 +4,8 @@ from Products.CMFCore.interfaces import IFolderish from zope.component.hooks import getSite +import urlparse + try: from zope.globalrequest import getRequest @@ -105,3 +107,18 @@ def _replace_variable_by_path(url, variable, obj): ) return url + + +def is_absolute(url): + """Return ``True``, if url is an absolute url. + See: https://stackoverflow.com/a/8357518/1337474 + """ + return bool(urlparse.urlparse(url).netloc) + + +def is_same_domain(url1, url2): + """Return ``True``, if url1 is on the same protocol and domain than url2. + """ + purl1 = urlparse.urlparse(url1) + purl2 = urlparse.urlparse(url2) + return purl1.scheme == purl2.scheme and purl1.netloc == purl2.netloc